# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##

__all__ = [
#           'cardsFaceUp',
#           'cardsFaceDown',
#           'isRankSequence',
#           'isAlternateColorSequence',
#           'isSameColorSequence',
#           'isSameSuitSequence',
#           'isAnySuitButOwnSequence',
#           'getNumberOfFreeStacks',
           'getPileFromStacks',
           'Stack',
           'DealPilesMethods',
           'DealBaseMethods',
           'Redeal_cards_StackMethods',
           'Talon',
           'DealRowTalon',
           'InitialDealTalon',
           'RedealTalon',
           'DealRowRedealTalon',
           'DealReserveRedealTalon',
           'SpiderTalon',
           'GroundsForADivorceTalon',
           'OpenStack',
           'AbstractFoundation',
           'SS_Foundation',
           'RK_Foundation',
           'AC_Foundation',
           'SC_Foundation',
#           'Spider_SS_Foundation',
#           'Spider_AC_Foundation',
#           'Spider_RK_Foundation',
#           'Spider_DropMethods',
           'BasicRowStack',
           'SequenceRowStack',
           'AC_RowStack',
           'SC_RowStack',
           'SS_RowStack',
           'RK_RowStack',
           'BO_RowStack',
           'UD_AC_RowStack',
           'UD_SC_RowStack',
           'UD_SS_RowStack',
           'UD_RK_RowStack',
#           'FreeCell_AC_RowStack',
#           'FreeCell_SS_RowStack',
#           'FreeCell_RK_RowStack',
#           'Spider_AC_RowStack',
#           'Spider_SS_RowStack',
#           'Yukon_AC_RowStack',
#           'Yukon_SS_RowStack',
#           'Yukon_RK_RowStack',
#           'KingAC_RowStack',
#           'KingSS_RowStack',
#           'KingRK_RowStack',
           'WasteStack',
           'WasteTalon',
           'FaceUpWasteTalon',
           'OpenTalon',
           'ReserveStack',
           'SuperMove_StackMethods',
           'SuperMoveSS_RowStack',
           'SuperMoveAC_RowStack',
           'SuperMoveRK_RowStack',
           'SuperMoveSC_RowStack',
           'SuperMoveBO_RowStack',
           'InvisibleStack',
           'StackWrapper',
           'WeakStackWrapper',
           'FullStackWrapper',
           'ArbitraryStack',
           ]

import logging

Logger = logging.getLogger('PySolFC.Stack')

from ..utillib import misc
from ..packlib.info import ACE, KING, ANY, MAX, VARY
from ..viewlib.constants import ANCHOR_NW
from ..viewlib import canvas
from ..settings import CONF_OPT

from .. import comparecards as cmp_cards
from .caps import CapsStack as Capabilities
from .bind import BindStack_Methods
from .drag import DragStack_Methods
from .. import helpstrings as hs
from ..gamelib import state
from . import move as a_move

scale = lambda value, factor: int(round(value * factor))


def getPileFromStacks(stacks, reverse=False):
    """
    getPileFromStacks(stacks:tuple, reverse:bool=False) -> list

    Copy all top cards of stacks into a new sequence of cards when each has
    cards and is face-up.

    :param sequence stacks: pile group.
    :keyword boolean reverse: reverse new sequence?
    :return: new sequence of top cards.
    :rtype: sequence
    """
    cards = []
    for s in stacks:
        if not s.cards or not s.TOPCARD.face_up:
            return None
        cards.append(s.TOPCARD)
    if reverse:
        cards.reverse()
    return cards


class Stack(DragStack_Methods, BindStack_Methods):
    """
    A generic stack of cards used as the base class for all other stacks. The
    default event handlers turn the top card of the stack with its face up on
    a (single or double) click, and also support moving a subpile around.

    :ivar integer MIN_VISIBLE_XOFFSET: min. width visible for a lower card.
    :ivar integer MIN_VISIBLE_YOFFSET: min. height visible for a lower card.
    :ivar float SHRINK_FACTOR: factor to reduce offset for a face-down card.
    :ivar string _BOTTOM_IMAGE_: use a blank bottom image.
    """
    MIN_VISIBLE_XOFFSET = 3
    MIN_VISIBLE_YOFFSET = 3
    SHRINK_FACTOR = 2.
    _BOTTOM_IMAGE_ = 'blank'

    def __init__(self, x, y, game, offset=(0, 0), **cap):
        """
        __init__(x:int, y:int, game:obj[, offset=(0, 0), *capability* {}]) ->
          None

        :cvar integer id: number of pile in total.
        :cvar object game: game
        :cvar tuple cards: pile's cards.
        :cvar boolean is_filled: pile is full
        :cvar Capable: pile's capability.
        :cvar tuple init_coord: pile's initial position. (x, y)
        :cvar integer x: x
        :cvar integer y: y
        :cvar object canvas: game.canvas
        :cvar integer|tuple CARD_XOFFSET: pile's per card x-offset.
        :cvar tuple INIT_CARD_OFFSETS: pile's per card initial offset.
        :cvar integer|tuple CARD_YOFFSET: pile's per card y-offset.
        :cvar integer|tuple INIT_YOFFSET: pile's per card initial y-offset.
        :cvar object canvas.GroupItem group = canvas group for pile's items
        :cvar object images: pile's base and shadow image
        :cvar object items: pile's base and shadow item
        :cvar object texts: pile's base and shadow item
        :cvar object top_bottom: top base image.
        :cvar boolean is_visible: pile's visibility on ``game.canvas``
        :cvar boolean is_open: pile's playability on ``game.canvas``
        :cvar boolean can_hide_cards: pile's ability to hide cards.
        :cvar object current_cursor:  pile's cursor choice
        :cvar boolean cursor_changed: pile's cursor changed
        :cvar float shrink_face_down: offset reduction for a face-down card.

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """

        id_ = len(game.allstacks)
        game.allstacks.append(self)
        mapkey = misc.Point(int(x), int(y))
        game.stackmap[mapkey] = id_
        self.id = id_
        self.game = game
        self.cards = []
        self.is_filled = False
        capable = type('Capable', (Capabilities, ), {})
        self.cap = capable(**cap)
        self.x, self.y = self.init_coord = mapkey
        self.canvas = game.canvas
        self.CARD_XOFFSET, self.CARD_YOFFSET = self.INIT_CARD_OFFSETS = offset
        self.INIT_CARD_YOFFSET = offset[1]  # for reallocateCards
        self.group = canvas.GroupItem(self.canvas)
        self.shrink_face_down = 1
        self.images = dict(bottom=None, shade_img=None)  # cache image cache
        self.items = dict(bottom=None, shade_item=None)  # canvas image itms
        self.texts = {'ncards': None, 'misc': None}        # canvas text items
        self.top_bottom = None                             # highest base item
        margin = self.canvas._cinfo_margin._add_size(self.game.images.size)
        self.is_visible = self.x >= -margin.x and self.y >= -margin.y
        self.is_open = None
        self.can_hide_cards = None
        self.max_shadow_cards = None
        self.current_cursor = ''
        self.cursor_changed = False

    @property
    def coord(self):
        return misc.Point(x=self.x, y=self.y)

#    def destruct(self):
#        """
#        destruct() -> None

#        Assist destruction of canvas group items with possible circular refs.

#        :raises: AssertionError: Class is not root class method.
#        """
#        assert not hasattr(super(Stack, self), 'destruct')
#        unbind_destroy(self.group)

    def assertStack(self):
        """
        assertStack() -> None

        Sanity checks for the pile.

        :raises: AssertionError: Class is not root class method.
        :raises: AssertionError: Impossible cap['min_accept'] and cap['min_move'].
        :raises: AssertionError: Impossible cap['max_move'] for no-offset pile.
        """
        assert not hasattr(super(Stack, self), 'assertStack')
        assert self.cap['min_move'] > 0 and self.cap['min_accept'] > 0
        offset = (sum(self.CARD_XOFFSET), sum(self.CARD_YOFFSET))
        assert not offset == (0, 0) or self.cap['max_move'] in (0, 1)

    def prepareStack(self):
        """
        prepareStack() -> None

        Pile preparation for the canvas. prepareView() and if is_visible call
        initbindings() to enable for mouse and keyboard interactivty.
        """
        self.prepareView()
        if self.is_visible:
            self.initBindings()

    def prepareView(self):
        """
        prepareView() -> None

        Prepare internal pile values for use on the canvas. prepareBottom()
        to set possible bottom image.

        :cvar tuple CARD_XOFFSET: set as tuple of integer x-offsets.
        :cvar tuple INIT_CARD_OFFSETS: set to CARD_XOFFSET, CARD_YOFFSET
        :cvar tuple CARD_YOFFSET: set as tuple of integer y-offsets.
        :cvar tuple INIT_YOFFSET: set to CARD_XOFFSET
        :cvar boolean is_open: Set pile's playability.
        :cvar boolean can_hide_cards: Set pile's ability to hide cards.

        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'prepareView')
        ox, oy = self.CARD_XOFFSET, self.CARD_YOFFSET
        if ox == 0 and oy == 0:
            assert self.cap['max_move'] <= 1
        w = self.CARD_XOFFSET = ((ox,) if isinstance(ox, int) else
                                 tuple([int(round(x)) for x in ox]))
        h = self.CARD_YOFFSET = ((oy,) if isinstance(oy, int) else
                                 tuple([int(round(y)) for y in oy]))
        # Preserve offsets for resize() and updatePositions()
        self.INIT_CARD_OFFSETS = (self.CARD_XOFFSET, self.CARD_YOFFSET,)
        self.INIT_CARD_YOFFSET = self.CARD_YOFFSET
        # Prepare common checks for pile
        if self.can_hide_cards is None:
            self.can_hide_cards = self.is_visible
            if self.cap['max_cards'] < 3:
                self.can_hide_cards = False
            elif any([v for v in w + h if v > 0]):
                self.can_hide_cards = False
            elif self.canvas.preview:
                self.can_hide_cards = False
        if self.is_open is None:
            self.is_open = False
            if (self.is_visible and
                (abs(w[0]) >= self.MIN_VISIBLE_XOFFSET or
                 abs(h[0]) >= self.MIN_VISIBLE_YOFFSET)):
                self.is_open = True
        self.max_shadow_cards = self.max_shadow_cards or MAX
        if (CONF_OPT['pile_shrink'] and
            isinstance(ox, int) and isinstance(oy, int)):
            # no shrink if xoffset/yoffset too small
            factor = self.SHRINK_FACTOR
            offset = self.game.images.offset
            if ((ox == 0 and oy >= offset.y / factor) or
                (oy == 0 and ox >= offset.x / factor)):
                self.shrink_face_down = factor
        # bottom image
        if self.is_visible:
            self.prepareBottom()

    def prepareBottom(self):
        """
        prepareBottom() -> None

        Prepare bottom image to allow events to be recieved even when empty.

        :cvar canvas.ImageItem images.bottom: ImageItem of getBottomImage().
        :cvar canvas.ImageItem top_bottom: images.bottom.

        :raises: AssertionError: Class is not root class method.
        :raises: AssertionError: Bottom image must be None while is_visible.
        """
        assert not hasattr(super(Stack, self), 'prepareBottom')
        assert self.is_visible and self.images['bottom'] is None
        img = self.getBottomImage()
        if img is not None:
            self.images['bottom'] = canvas.ImageItem(self.canvas, self.coord,
                                                    group=self.group, image=img)
            self.top_bottom = self.images['bottom']

    def prepareInvisibleBottom(self):
        """
        prepareInvisibleBottom() -> None

        Prepare bottom image to allow events to be recieved even when empty.

        :cvar canvas.RectangleItem images.bottom: RectangleItem of size.
        :cvar canvas.RectangleItem top_bottom: images.bottom.

        :raises: AssertionError: Class is not root class method.
        :raises: AssertionError: Bottom image must be None while is_visible.
        """
        assert not hasattr(super(Stack, self), 'prepareInvisibleBottom')
        assert self.is_visible and self.items['bottom'] is None
        size = self.game.images.size
        self.items['bottom'] = canvas.RectangleItem(self.canvas,
                                            (self.coord, size,),
                                            fill=None, outline=None, width=0,
                                            group=self.group)
        self.top_bottom = self.items['bottom']

    def refreshView(self):
        """
        refreshView() -> None

        Fully update the pile on the canvas. Updates hiding, card positions
        and stacking order.

        :raises: AssertionError: Class is not root class method.

        important::
          Avoid calling as its rather slow.
        """
        assert not hasattr(super(Stack, self), 'refreshView')
        cards = self.cards
        if not self.is_visible or len(cards) < 2:
            return
        if self.can_hide_cards:
            # hide all lower cards
            for c in cards[:-2]:
                Logger.info("hide (%s=%s)", c, c.hide_stack)
                c.hide(self)
            # unhide the 2 top cards
            for c in cards[-2:]:
                Logger.info("unhide (%s=%s)", c, c.hide_stack)
                c.unhide()
        # update the card postions and stacking order
        item = cards[0]._item
        x, y = self.x, self.y
        ix, iy, lx, ly = 0, 0, len(self.CARD_XOFFSET), len(self.CARD_YOFFSET)
        for c in cards[1:]:
            c._item.raise_item(item)
            item = c._item
            if not self.can_hide_cards:
                d = int(c.face_up) or self.shrink_face_down
                x += int(self.CARD_XOFFSET[ix] / d)
                y += int(self.CARD_YOFFSET[iy] / d)
                ix = (ix + 1) % lx
                iy = (iy + 1) % ly
                Logger.info("move (%s=[%s, %s])", c, x, y)
                c.move_to(x, y)

    def _repositionItems(self, item, xf, yf):
        """
        _repositionItems(item:obj, xf:int, yf:int) -> None

        Reposition items to new coordinates scaled with xf and yf.

        :param object item: canvas item or image.
        :param integer xf: a muliple transform factor from original size.
        :param integer yf: a muliple transform factor from original size.
        :raises: AssertionError: Class is not root class method.
        :raises: AssertionError: Item missing init_coord and/or move_to attr.
        """
        assert not hasattr(super(Stack, self), '_repositionItems')
        assert hasattr(item, 'init_coord') and hasattr(item, 'move_to')
        ix, iy = item.init_coord
        item.move_to(scale(ix, xf), scale(iy, yf))

    def resize(self, xf, yf):
        """
        resize(xf, yf) -> None

        Resize and reposition items and images related to pile.

        :cvar tuple CARD_XOFFSET: Scale x offsets with xf.
        :cvar tuple CARD_YOFFSET: Scale y offsets with yf.
        :cvar tuple INIT_CARD_YOFFSET: Set to CARD_YOFFSET.
        :cvar object images.bottom: Scale image and reposition.
        :cvar object items.bottom: Reposition.
        :cvar object items.shade_items: Scale image and reposition.
        :cvar object texts.ncards: Reposition.
        :cvar object texts.misc: Reposition.

        :param integer xf: a muliple transform factor from original size.
        :param integer yf: a muliple transform factor from original size.

        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'resize')
        Logger.info("id (%s) resize(%s, %s)", self.id, xf, yf)
        Logger.info("is_visible (%s)", self.is_visible)
        x0, y0 = self.init_coord
        x, y = scale(x0, xf), scale(y0, yf)
        self.x, self.y = x, y
        # Scale offsets
        xoffset = tuple(scale(i, xf) for i in self.INIT_CARD_OFFSETS[0])
        yoffset = tuple(scale(i, yf) for i in self.INIT_CARD_OFFSETS[1])
        self.CARD_XOFFSET, self.CARD_YOFFSET = xoffset, yoffset
        self.INIT_CARD_YOFFSET = yoffset
        Logger.info('offset (%s, %s)', xoffset, yoffset)
        # Repostion cards
        for card in self.cards:
            card.move_to(*self.getPosition(card))
        if not self.is_visible:
            return
        # Reposition bottom images
        images, items, texts = self.images, self.items, self.texts
        if images['bottom']:
            images['bottom'].config(image=self.getBottomImage())
            images['bottom'].move_to(x, y)
        # Reposition bottom items
        if items['bottom']:
            items['bottom'].coords(*[scale(i, f) for i, f in
                                  zip(items['bottom'].coords(), (xf, yf) * 2)])
        # Reposition shade items
        if items['shade_item']:
            img = self.game.images.highlight
            if img:
                items['shade_item'] = img
            items['shade_item'].move_to(x, y)
        # Reposition text items
        for key in (k for k in ('ncards', 'misc') if texts.get(k)):
            self._repositionItems(texts[key], xf, yf)

    # Core access methods
    def __hideCards(self, card):
        """
        Ensure the display of the 2 top cards. Set card._item.addtag group.

        :param object card: card being added to pile
        """
        if (self.can_hide_cards and len(self.cards) >= 3 and
            card in self.cards[-2:]):
            self.cards[-3].hide(self)
        card._item.addtag(self.group)

    def __unhideCards(self, card, unhide=True, **kw):
        """
        Ensure the display of the 2 top cards. Set card._item.dtag group.

        :param object card: card being removed from pile.
        :param boolean unhide: whether to unhide cards.
        """
        card._item.deltag(self.group)
        if unhide and self.can_hide_cards:
            card.unhide()
            if len(self.cards) >= 3 and card in self.cards[-2:]:
                self.cards[-3].unhide()

    def addCard(self, card, **kw):
        """
        addCard(card:obj[, unhide:bool=True, update:bool=True]) -> obj

        Add card onto top of the stack.

        :cvar tuple cards: card append to pile's cards.

        :param object card: card being added to pile.
        :keyword boolean unhide: tkraise() card?
        :keyword boolean update: _update_text()?
        :return: card
        :rtype: object

        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'addCard')
        self.cards.append(card)
        card.raise_item(unhide=kw.get('unhide', True))
        self.__hideCards(card)
        self._position(card)
        if kw.get('update', True):
            self._update_text()
        self.closeStack()
        return card

    def insertCard(self, card, position, **kw):
        """
        insertCard(card:obj, position:int[, unhide:bool, update:bool=True]) ->
          obj

        Insert card into the stack.

        :cvar tuple cards: insert card at position in pile.

        :param object card: card being placed.
        :param integer position: postion in pile.
        :keyword boolean unhide: tkraise() card?
        :keyword boolean update: _update_text()?
        :return: card
        :rtype: object

        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'insertCard')
        self.cards.insert(position, card)
        for c in self.cards[position:]:
            c.raise_item(unhide=kw.get('unhide', True))
        self.__hideCards(card)
        for c in self.cards[position:]:
            self._position(c)
        if kw.get('update', True):
            self._update_text()
        self.closeStack()
        return card

    def removeCard(self, card=None, **kw):
        """
        removeCard([card:obj, unhide:bool, update:bool=True,
          update_postions:bool=False]) -> obj

        Remove card from the stack.

        :cvar tuple cards: pile's cards with card removed.
        :cvar boolean is_filled: pile is not full.

        :param object card: card being removed to pile.
        :keyword boolean unhide: unhide() card?
        :keyword boolean update: _update_text?
        :return: card
        :rtype: object

        :raises: AssertionError: Class is not root class method.
        :raises: AssertionError: Pile has no cards.
        """
        assert not hasattr(super(Stack, self), 'removeCard')
        assert self.cards
        if card is None:
            card = self.TOPCARD
            self.__unhideCards(card, **kw)
            self.cards.remove(card)
        else:
            self.__unhideCards(card, **kw)
            card_index = self.cards.index(card)
            self.cards.remove(card)
            if kw.get('update_positions', False):
                for c in self.cards[card_index:]:
                    self._position(c)
        if kw.get('update', True):
            self._update_text()
        self.unshadeStack()
        self.is_filled = False
        return card

    def _position(self, card):
        """
        _position(card:obj) -> None

        Position the card on the canvas.

        :param object card: card being positioned.

        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), '_position')
        card.move_to(*self.getPosition(card))

    def _locate(self, event):
        """
        _locate(event:obj) -> tup

        Locate the card at point of event.

        :param object event: canvas event.
        :return: position of card in pile
        :rtype: int
        :raises: AssertionError: Class is not root class method.

        note::
          Modified to not call Canvas.findCards() only for it to call
          findCardXY() for it reply.

          Changed name from _findCard() to align with other methods.
        """
        assert not hasattr(super(Stack, self), '_locate')
        if event is not None and self.cards:
            return self.canvas.locate(self, event)

    def updateModel(self, undo, flags):
        """
        updateModel(undo:obj, flags:int) -> None

        Update pile attributes and *capability* after an undo / redo event.
        The default is for the pile no to be updated. Many stacks subclass
        method for their mutable attributes and *capability*.

        :param object undo: atomic move
        :param integer flags: undo or redo mode.
        :raises: AssertionError: Class is not root class method.

        seealso::
          pysollib.move.AUpdateStackMove
        """
        assert not hasattr(super(Stack, self), 'updateModel')
        pass

    def copyModel(self, clone):
        """
        copyModel(clone:obj) -> None

        Update clone with id, game and cap attributes of pile.

        :param object clone: empty clone stack to fill.
        :raises: AssertionError: Class is not root class method.

        seealso::
          pysollib.hint.AClonedStack
        """
        assert not hasattr(super(Stack, self), 'copyModel')
        clone.__dict__ = dict(id=self.id, game=self.game, cap=self.cap)

    def updatePositions(self):
        """
        updatePositions() -> None

        Compact the YOFFSET of stack when a cards goes off screen.

        :cvar tuple CARD_YOFFSET: Expand or compact the pile y-offset.

        :raises: AssertionError: Class is not root class method.

        note::
            Method incorporates former reallocateCards().
        """
        assert not hasattr(super(Stack, self), 'updatePositions')
        if (not CONF_OPT['pile_compact'] or self.CARD_XOFFSET != (0,) or
            len(self.CARD_YOFFSET) != 1 or self.CARD_YOFFSET[0] <= 0 or
            len(self.cards) <= 1 or not self.canvas.winfo_ismapped()):
            return
        game = self.game
        yoffset = self.CARD_YOFFSET[0]
        cardh = game.images.size.height / 2  # 1/2 card is visible
        num_up = len([c for c in self.cards if c.face_up])
        num_dn = len(self.cards) - num_up
        nset = lambda yo: num_dn * yo / self.shrink_face_down + num_up * yo
        stack_height = int(self.y + nset(yoffset) + cardh)
        if CONF_OPT['scale_auto']:
            height = self.canvas.winfo_height()
        else:
            height = max(self.canvas.winfo_height(),
                         game.height + 2 * self.canvas._cinfo_margin.y)
        Logger.info("height (%s/%s)", stack_height, height)
        dy = float(height - self.y - cardh) / nset(1)
        if stack_height == height:
            return
        elif stack_height > height:  # compact stack
            if dy >= yoffset:
                return
            Logger.info("compact (%s)", dy)
        elif stack_height < height:  # expand stack
            if self.CARD_YOFFSET == self.INIT_CARD_YOFFSET:
                return
            dy = min(dy, self.INIT_CARD_YOFFSET[0])
            Logger.info("expand (%s)", dy)
        # Reposition with new offset
        self.CARD_YOFFSET = (dy,)
        for c in self.cards:
            self._position(c)

    # Capabilities - important for game logic
    def cardsBlock(self):
        """
        cardsBlock() -> bool

        Check if pile is blocked. The default does not allow *capablity* to
        build, play, flip, or drop. Many stacks subclass method for more or
        less restrictive blocking. Many indicate blocked piles within a group.

        :return: pile is blocked?
        :rtype: boolean
        :raises: AssertionError: Class is not root class method.

        note::
          Changed from basicIsBlocked() to align all *capability* checks.

          Default return changed to True allowing method to block Building.
        """
        assert not hasattr(super(Stack, self), 'cardsBlock')
        return True

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check if pile can build with cards from from_stack. The default asks
        *capability* if builds which in addition also asks cardsBlock().
        Many stacks subclass method for more or less restrictive building.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean
        :raises: AssertionError: Class is not root class method.

        seealso:
          Method pysollib.stackcaps.builds for the *capability* check.

        note::
          Changed from acceptsCards() to align all *capability* checks.

          Default return changed to check build *capability* checks.
        """
        assert not hasattr(super(Stack, self), 'cardsBuild')
        return self.cap.builds(self, from_stack, cards)

    def cardsPlay(self, cards):
        """
        cardsPlay(cards:tup) -> bool

        Check if pile can play assumed top cards. The default asks
        *capability* if plays which in addition also asks cardsBlock(). Many
        stacks subclass method for more or less restrictive playing.

        :param sequence cards: cards from sending pile.
        :return: pile moves cards?
        :rtype: boolean
        :raises: AssertionError: Class is not root class method.

        seealso:
          Method pysollib.stackcaps.plays for the *capability* check.

        note::
          Changed from canMoveCards() to align all *capability* checks.

          Default return changed to check build *capability* checks.
        """
        assert not hasattr(super(Stack, self), 'cardsPlay')
        return self.cap.plays(self, cards)

    def cardFlips(self):
        """
        cardFlips() -> bool

        Check if pile can flip face down top card. The default will check for
        cardsBlock() and pile has cards. Many stacks subclass method for more
        or less restrictive flipping.

        :return: pile flips top card?
        :rtype: boolean
        :raises: AssertionError: Class is not root class method.

        note::
          Changed from canFlipCard() to align all *capability* checks.
        """
        assert not hasattr(super(Stack, self), 'cardFlips')
        if self.cardsBlock() or not self.cards:
            return False
        return not self.TOPCARD.face_up

    def cardsDrop(self, stacks, event=None):
        """
        cardsDrop(stacks:tup, event:obj) -> tup

        Check if pile can drop its top sequence of cards. The default will
        check cardsBlock() and pile has cards, then if stacks cardsBuild().
        Many stacks subclass method for more or less restrictive dropping.

        :param tuple stacks: usually game.s.foundations
        :return: pile receiving dropped cards and number of cards.
        :rtype: tuple
        :raises: AssertionError: Class is not root class method.

        note::
          Changed from canDropCard() to align all *capability* checks.
        """
        assert not hasattr(super(Stack, self), 'cardsDrop')
        if self.cardsBlock() or not self.cards:
            return (None, 0)
        cards = self.cards[-1:]
        if self.cardsPlay(cards):
            for stack in stacks:
                if stack is not self and stack.cardsBuild(self, cards):
                    return (stack, 1)
        return (None, 0)

    # State
    def reset(self):
        """
        Called when starting a new game.

        :cvar Offset CARD_YOFFSET: reset values to INIT_CARD_YOFFSET.
        :cvar object images.shade_item: unset item.
        :cvar object items.shade_img: unset image.
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'reset')
        self.CARD_YOFFSET = self.INIT_CARD_YOFFSET
        self.items['shade_item'] = None
        self.images['shade_img'] = None

    def __repr__(self):
        """
        helpful class descriptor for debug statements.

        :return: flip |top|?
        :rtype: boolean
        """
        return "{0} (id: {1})".format(self.__class__.__name__, self.id)

    # Atomic move actions
    def flip_card(self, **kwargs):
        """
        flip_card([**animation keywords**]) -> None

        Flip top card of pile.

        :keyword boolean animation: animate move?
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'flip_card')
        self.game._move_do(a_move.AFlipMove(self, **kwargs),
                           reset=True)  # low-level

    def move_pile(self, ncards, to_stack, **kwargs):
        """
        Move top ncards of pile to stack then call fill stack.

        .. move_pile(ncards:int, to_stack:Pile[, *animation keywords*]) -> None

        *raises:* AssertionError when class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'move_pile')
        self.game._move_do(a_move.AMoveMove(ncards, self, to_stack, **kwargs),
                           reset=True)  # low level
        self.fill_stack()

    def fill_stack(self):
        """
        Perfom actions after a move_pile(). The default calls
        Game._auto_fill() with pile. Many stacks subclass method to deal
        new cards when pile has no cards.

        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'fill_stack')
        self.game._auto_fill(self)

    def closeStack(self):
        """
        closeStack() -> None

        Perfom actions after an addCard() or insertCard(). Mainly foundation
        stacks subclass method to shade when pile has a completed sequence.

        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'closeStack')
        pass

    # Appearance
    def getBottomImage(self):
        """
        getBottomImage() -> obj

        Use _BOTTOM_IMAGE_ to Images.bottom(). Also pass the base card value
        for 'suit' and 'rank'

        :return: pile's blank base image.
        :rtype: Image
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'getBottomImage')
        bottom = self._BOTTOM_IMAGE_ or ''
        index = -1
        if bottom in ('suit', 'rank'):
            index = self.cap['base_{0}'.format(bottom)]
        return self.game.images.bottom(bottom, index)

    @property
    def TOPCARD(self):
        """
        Top Card Property

        :return: top card of pile or None.
        :rtype: object | None
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'TOPCARD')
        return self.cards[-1] if self.cards else None

    @property
    def SEQUENCE(self):
        """
        Moveable Sequence Property

        :return: largest cardsPlay() pile of cards or None.
        :rtype: object | None
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'SEQUENCE')
        cap = self.cap
        if self.cards and cap['max_move'] > 0:
            cards = self.cards[-cap['max_move']:]
            Logger.info("length (%s/%s)", len(cards), cap['min_cards'])
            while len(cards) > cap['min_cards']:
                if self.cardsPlay(cards):
                    Logger.info("move %s cards", len(cards))
                    return cards
                cards.pop(0)

    @property
    def BASECARD(self):
        """
        Base Card property

        :return: base card of pile or None.
        :rtype: object | None
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'SEQUENCE')
        return self.cards[0] if self.cards else None

    def getPosition(self, card=None):
        """
        getPosition(card:obj=None) -> seq

        Card or TOPCARD position on the canvas.

        :param object card: card
        :return: left-top of ``card``
        :rtype: sequence
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'getPosition')
        x, y = self.x, self.y
        if self.can_hide_cards:
            return x, y
        thecard = self.TOPCARD if card is None else card
        ix, iy, lx, ly = 0, 0, len(self.CARD_XOFFSET), len(self.CARD_YOFFSET)
        for card in self.cards:
            if card is thecard:
                break
            d = 1 if card.face_up else self.shrink_face_down
            x += self.CARD_XOFFSET[ix] / d
            y += self.CARD_YOFFSET[iy] / d
            ix = (ix + 1) % lx
            iy = (iy + 1) % ly
        return int(x), int(y)

    TOPPOSITION = property(getPosition)  # Top card position

    def getOffset(self, card=None):
        """
        getOffset(card:obj) -> seq

        Card or TOPCARD offset from next card on the canvas.

        :param object card: card
        :return: width-height of offset.
        :rtype: sequence
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'getOffset')
        if self.can_hide_cards:
            return 0, 0
        thecard = self.TOPCARD if card is None else card
        lx, ly = len(self.CARD_XOFFSET), len(self.CARD_YOFFSET)
        i = list(self.cards).index(thecard)
        return misc.Offset(self.CARD_XOFFSET[i % lx], self.CARD_YOFFSET[i % ly])

    TOPOFFSET = property(getOffset)  # Top card offset

    @state.not_state('is_preview')
    def _update_text(self):
        """
        _update_text() -> None

        Update number of cards on a stack.

        :raises: AssertionError: Class is root class method.

        note:: Using text_format='%D' provides a special counter which shows
          only if pile has cards.
        """
        assert not hasattr(super(Stack, self), '_update_text')
        if self.texts['ncards'] is None:
            return
        format_ = self.texts['ncards'].text_format or '%d'
        if format_ == '%D':
            format_ = '%d' if self.cards else ''
        if format_:
            text = format_ % len(self.cards)
        self.texts['ncards'].config(text=text)

    def basicShallHighlight(self, card):
        """
        basicShallHighlight(card:obj) - > bool

        Default is to make all open stacks available for highlighting.

        :param object card: card
        :return: can highlight?
        :rytpe: boolean
        :raises: AssertionError: Card is not in pile
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'basicShallHighlight')
        assert card in self.cards
        if not self.is_visible or not card.face_up:
            return False
        if card is self.TOPCARD:
            return True
        if not self.is_open:
            return False
        return True

    def __can_highlight_iter(self, thecard):
        for stack in self.game.allstacks:
            for card in (c for c in stack.cards
                         if not c is card and stack.basicShallHighlight(c)):
                yield stack, card

    def highlightSameRank(self, event):
        """
        highlightSameRank(event:obj) - > bool

        :param object event: canvas event
        :return: can highlight?
        :rytpe: boolean
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'highlightSameRank')
        i = self._locate(event)
        if i < 0:
            return

        thecard = self.cards[i]
        if not self.basicShallHighlight(thecard):
            return

        info = [(self, thecard, thecard, CONF_OPT['colors']['same_1'],)]
        info.extend((s, c, c, CONF_OPT['colors']['same_2'],)
                     for s, c in self.__can_highlight_iter(thecard)
                     if c['rank'] == thecard['rank'])
        self.game.view_match('same', info)

    def highlightMatchingCards(self, event):
        """
        highlightMatchingCards(event:obj) - > bool

        :param object event: canvas event
        :return: can highlight?
        :rytpe: boolean
        :raises: AssertionError: Class is not root class method.

        .. note:: elminated Game.shallHighlightMatch() in favor of asking
          stacks directly if they acceptCards().
        """
        assert not hasattr(super(Stack, self), 'highlightMatchingCards')
        i = self._locate(event)
        if i < 0:
            return False

        foundations = self.game.s['foundations']
        thecard = self.cards[i]
        if not self.basicShallHighlight(thecard):
            return False

        card1 = card2 = thecard
        info = []
        for stack, card in self.__can_highlight_iter(thecard):
            # continue if both stacks are foundations
            if self in foundations and stack in foundations:
                continue
            if self.cardsBuild(stack, [card]):
                if stack is self:
                    j = self.cards.index(card)
                    if i - 1 == j:
                        card1 = card
                        continue
                    if i + 1 == j:
                        card2 = card
                        continue
                info.append((stack, card, card, CONF_OPT['colors']['cards_1']))
        if len(info) > 0:
            info.append((self, card1, card2, CONF_OPT['colors']['cards_2']))
        return self.game.view_match('cards', info)

    def _shadeStack(self):
        """
        _shadeStack() -> None

        Used by a foundation closeStack() to indicate a complete sequence.

        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), '_shadeStack')
        if not CONF_OPT['mark_filled']:
            return
        item = canvas.ImageItem(self.canvas, self.TOPCARD.coord, group=self.group,
                           image=self.game.images.highlight)
        self.items['shade_item'] = item

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), '_helpString')
        return str(self)

#    def _helpString(self):
#        """
#        _helpString -> str

#        :return: helpful string about pile for its canvas label.
#        :rtype:  base_string
#        :raises: AssertionError: Class is not root class method.
#        """
#        assert not hasattr(super(Stack, self), 'getHelp')
#        return "{0} {1}".format(_('Stack.'), self._helpString)

    def getBaseCard(self):
        """
        stub for a subclass. Show a string for card's required base value.
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'getBaseCard')
        return ''

    def _getBaseCard(self, rank=None):
        """
        _getBaseCard([rank:int=None]) -> str

        Using game._RANKS_ return a string for a given rank or cap['base_rank']
        if cap['max_accept'] is greater than 0.
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), '_getBaseCard')
        if self.cap['max_accept'] == 0:
            return ''
        br = self.cap['base_rank'] if rank is None else rank
        return _('Base card - {0}.').format(self.game._RANKS_.get(br, br + 1))

    def getNumCards(self):
        """
        _getNumCards([rank:int=None]) -> str

        return a string for the number of cards in the stack.
        :raises: AssertionError: Class is not root class method.
        """
        assert not hasattr(super(Stack, self), 'getBaseCard')
        n = len(self.cards)
        return (_('No cards') if n == 0 else
                p_.ngettext('%d card', '%d cards', n) % n)


class DealPilesMethods(object):
    """
    Supports a concept of dealing cards to a group of piles. Each public
    method returns number of cards dealt.
    """

    def dealRow(self, rows=None, sound=False, **kw):
        """
        dealRow([rows:tup=Game.s.rows, sound:bool=False, flip:bool=True,
          reverse:bool=False, frames:int=-1, ]) -> int

        Deal cards to each pile of rows.

        :keyword sequence rows: piles recieving a card
        :keyword boolean flip: turn card face up?
        :keyword boolean reverse: reverse pile order?
        :keyword boolean sound: sound fx?
        :return: number of cards dealt
        :rtype: integer

        note:: Method is just a wrapper with sound support for dealToStacks().
        """
        rows = rows or self.game.s['rows']
        if sound and kw['frames'] and self.game.can_play_sound:
            self.game._play_sound('deal')
        ncards = self.dealToStacks(rows, **kw)
        self.game._stop_sound()
        return ncards

    def dealRowAvail(self, rows=None, sound=False, frames=-1, **kwargs):
        """
        dealRowAvail([rows:tup=Game.s.rows, flip:bool=True,
          reverse:bool=False, frames:int=-1, sound:bool=False]) -> int

        Deal cards to as many rows as pile has cards.

        :keyword sequence rows: piles recieving a card
        :keyword boolean flip: turn card face up?
        :keyword boolean reverse: reverse pile order?
        :keyword boolean sound: sound fx?
        :return: # cards dealt
        :rtype: integer

        note:: Method is just a wrapper with sound support for dealToStacks()
          and limits on rows passed to cards available.
        """
        rows = rows or self.game.s['rows']
        if sound and frames and self.game.can_play_sound:
            self.game._play_sound('deal')
        n = self.dealToStacks(rows[:len(self.cards)], **kwargs)
        self.game._stop_sound()
        return n

    @state.move_state('deal')
    def dealToStacks(self, stacks, flip=True, reverse=False, **kwargs):
        """
        dealToStacks(stacks[, flip=True, reverse=False, frames=-1,
          shadows=-1]) -> int

        Deal cards to each stacks.

        :Keywords:
          rows : tuple(Stack...)
            Pile type recieving cards. Default is game.s.rows.
          flip : bool
            Turn card face up?
          reverse : bool
            Deal rows in reverse order?

        :returns: Number of cards dealt.

        :raises:
          AssertionError: Not enough cards for number of piles.
          AssertionError: TOPCARD.face_up.
          AssertionError: stacks include self.
        """
        if not self.cards or not stacks:
            return 0
        assert len(self.cards) >= len(stacks)
        if reverse:
            stacks = list(stacks)
            stacks.reverse()
        for row in stacks:
            assert not self.TOPCARD.face_up and row is not self
            self.game.deal(self, row, flip=flip, **kwargs)
        return len(stacks)

    @state.move_state('deal')
    def deal_row_or_drop(self, rows=None, ncards=1, flip=True,
                             reverse=False, **kw):
        """
        dealRowOrFoundations([rows=Game.s.rows, ncards=1, flip=True,
          reverse=False, frames=-1, shadows=-1]) -> int

        Deal cards to each row or finish ncards without replacement.

        :Keywords:
          rows : tuple(Stack...)
            Pile type recieving cards. Default is game.s.rows.
          ncards : int
            Num cards to finish per foundation.
          reverse : bool
            Deal rows in reverse order?

        :returns: Number of cards dealt.
        """
        num_cards = 0
        rows = rows or self.game.s['rows']
        if not self.cards or not rows:
            return num_cards
        assert len(self.cards) >= len(rows)
        if reverse:
            rows = list(rows)
            rows.reverse()
        foundations = self.game.s['foundations']
        for row in rows:
            if not self.cards:
                break
            num_cards += 1
            pile = cmp_cards.groupTakes(foundations, self) or row
            # Ensure only ncards added to foundations.
            if pile in foundations and len(pile.cards) >= ncards + 1:
                pile = row
            self.game.deal(self, pile, flip=flip, **kw)
        return ncards

    def deal_row_and_drop(self, rows=None, ncards=1, flip=True,
                              reverse=False, **kw):
        """
        deal_row_and_drop([rows=Game.s.rows, ncards=1, flip=True,
          reverse=False, frames=-1, shadows=-1]) -> int

        Deal cards to each row or finish ncards with replacement.

        :Keywords:
          rows : tuple(Stack...)
            Pile type recieving cards. Default is game.s.rows.
          ncards : int
            Num cards to finish per foundation.
          flip : bool
            Turn card face up?
          reverse : bool
            Deal rows in reverse order?

        :returns: Number of cards dealt.
        """
        num_cards = 0
        rows = rows or self.game.s['rows']
        if not self.cards or not rows:
            return num_cards
        assert len(self.cards) >= len(rows)
        if reverse:
            rows = list(rows)
            rows.reverse()
        foundations = self.game.s['foundations']
        for row in rows:
            lrow = len(row.cards) + 1  # Only add 1 card to row
            while row.cards < lrow and self.cards:
                num_cards += 1
                pile = cmp_cards.groupTakes(foundations, self) or row
                # Ensure only ncards added to foundations.
                if pile in foundations and len(pile.cards) >= ncards + 1:
                    pile = row
                self.game.deal(self, pile, flip=flip, **kw)
        return num_cards


class DealBaseMethods(object):
    """
    Supports concept of dealing a random base_card, optionally saving modified
    *capabilty*. Each public method returns number of cards dealt.
    """

    def __getStacks(self, card):
        """
        __getStacks(card:obj) -> obj

        :keyword object card: current card.
        :return: pile to deal.
        :rtype: object
        :raises: AssertionError: Card found no pile with base_rank of ANY or
           that matches suit or already has cards.
        """
        suit = card['suit']
        for pile in self.game.s['foundations']:
            if not pile.cards and (pile.cap['base_rank'] == ANY or
                                   pile.cap['suit'] == suit):
                return pile
        raise AssertionError

    def dealBaseCards(self, ncards=1, saveinfo=True, **kwargs):
        """
        dealBaseCards([rows=game.s.foundations, saveinfo=True, ncards=1,
          frames=-1, shadows=-1]) -> int

        Deal 1 of ncards to each rows until none left.

        :Keywords:
          rows : tuple(Stack...)
            Pile type recieving cards. Default is game.s.foundations.

          ncards : int
            Num cards to finish per foundation.

          saveinfo : bool
            Save new base_rank capability?

        :returns: Number of cards dealt.

        :raises:
          AssertionError: `pysollib.game.Game.move_state` not `Game.S_INIT`
          AssertionError: Cards less than ncards

        note:: base_rank checking done by `__getStacks()` and base_rank saving
          done `pysollib.game.Game._save_stack_caps()`.
        """
        assert self.game.move_state == self.game.S_INIT
        assert ncards <= len(self.cards)
        saveinfo = kwargs.get('update_saveinfo', saveinfo)  # For compatability
        for i in range(ncards):
            card = self.TOPCARD
            stack = self.__getStacks(card)
            if saveinfo:
                self.game._save_stack_caps(stack, base_rank=card['rank'])
            kw['flip'] = not card.face_up
            self.dealToStacks([stack], **kwargs)
        return ncards


class Redeal_cards_StackMethods(object):
    """
    Supports concept of redealing cards from a pile group. Each public method
    returns number of cards dealt.
    """

    def _redeal(self, rows=None, reverse=False, **kwargs):
        """
        _redeal([rows=Game.s.rows, reverse=False, frames=0, shadows=0]) -> int

        Move all cards from rows back on this pile.

        :Keywords:
          rows : tuple(Stack...)
            Pile type returning cards. Default is game.s.rows.
          reverse : bool
            Deal rows in reverse order?

        :returns: Number of cards returned.

        :raise:
          AssertionError: Cards still available before redeal.
        """
        ncards = 0
        assert not self.cards
        rows = rows or self.game.s['rows']
        kwargs.setdefault('frames', 0)
        kwargs.setdefault('shadow', 0)
        if reverse:
            rows = list(rows)
            rows.reverse()
        for row in rows:
            while row.cards:
                ncards += 1
                self.game.deal(row, self, flip=row.TOPCARD.face_up, **kwargs)
        assert len(self.cards) == ncards
        return ncards

    def _shuffle(self, inverse=False, shuffle=False, **kw):
        """
        _shuffle([inverse=False, shuffle=False]) -> int

        Move all cards from rows back on this pile.

        :Keywords:
          rows : tuple(Stack...)
            Pile type returning cards. Default is game.s.rows.
          shuffle : bool
            Shuffle card order?
          inverse : bool
            Inverse card order?
        """
        if shuffle:
            self.game._move_do(a_move.AShuffleStackMove(self, self.game.random)
                                ) # low level
        if inverse:
            cards = list(self.cards)
            cards.reverse()
            self.cards = cards
        self.game._move_do(a_move.ANextRoundMove(self), reset=True)  # low level

    def redeal_cards(self, rows=None, sound=False, shuffle=False, inverse=True,
                    **kw):
        """
        redeal_cards([rows=game.s.rows, sound=True, shuffle=False,
          reverse=False, inverse=False, frames=0, shadows=0]) -> int

        Move all cards from rows back on this pile an opitonally shuffle,
        before Game.talon_redeal() new cards on rows.

        :Keywords:
          rows : tuple(Stack...)
            Pile type returning cards. Default is game.s.rows.
          reverse : bool
            Deal rows in reverse order?
          shuffle : bool
            Shuffle card order?
          inverse : bool
            Inverse card order?
          sound : bool
            Play sound fx?

        :returns: Number of cards returned.
        """
        if sound and self.game.can_play_sound:
            self.game._play_sound('deal')
        ncards = self._redeal(rows=rows, **kw)
        if ncards == 0:
            return ncards
        self._shuffle(**kw)
        self.game.talon_redeal()
        self.game._stop_sound()
        return ncards


class Talon(DealPilesMethods, DealBaseMethods, Stack):
    """
    Pile support dealing cards to other pile groups and base cards on
    Game.s.foundations.

    :ivar _BOTTOM_IMAGE: use shade bottom image.
    """
    _BOTTOM_IMAGE_ = 'shade'

    def __init__(self, x, y, game, offset=(0, 0), max_rounds=1, num_deal=1,
                 **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 0), max_rounds:int=1,
          num_deal:int=1, *capability* {}]) -> None

        :cvar integer max_rounds: number of times through pile.
        :cvar integer num_deal: number of cards each pile recieves at once.
        :cvar object images: pile's base and shadow image
        :cvar object items: pile's base and shadow item
        :cvar object texts: pile's base and shadow item

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left top per card on pile.
        :keyword integer max_rounds: number of times through pile.
        :keyword integer num_deal: number of cards each pile recieves at once.

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        super(Talon, self).__init__(x, y, game, offset, **cap)
        assert type(max_rounds) is int and VARY <= max_rounds <= MAX
        assert type(num_deal) is int and 0 <= num_deal <= MAX
        max_rounds = (500 if game.is_demo and max_rounds is MAX
                          else max_rounds)
        self.max_rounds = max_rounds
        self.num_deal = num_deal
        self.images.update({'redeal': None, 'redeal_img': None})
        self.texts.update({'rounds': None, 'redeal': None})
        self.init_redeal = dict(top_bottom=None, img_coord=None,
                                  txt_coord=None)
        self.reset()

    def reset(self):
        """
        In addition, resets round.

        :cvar int round: reset values to 1.
        """
        super(Talon, self).reset()
        self.round = 1

    def assertStack(self):
        """
        In addition, sanity checks for the pile. Check max_rounds is
        comparable to Game['redeals'].
        """
        super(Talon, self).assertStack()
        redeals = self.game['redeals']
        assert self.max_rounds == (redeals if redeals < 0 else redeals + 1)

    def clickHandler(self, event):
        """
        A <left-click> will Game.talon_deal() which usually returns with
        self.deal_cards() on this pile.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        return self.game.talon_deal(sound=True)

    def rightclickHandler(self, event):
        """
        rightclickHandler(event:obj) -> bool

        Treat <right-click> as <left-click>. Calls clickHandler() method.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        return self.clickHandler(event)

    def can_deal_cards(self):
        """
        Check by Game.talon_deal() if pile can deal more cards. Default is
        while there are cards available.

        :return: deal more cards?
        :rtype: bool
        """
        return bool(self.cards)

    def deal_cards(self, sound=False):
        """
        deal_cards([sound:bool=False]) -> None

        Deal cards for Game.talon_deal() to other piles. Default is nothing
        as cards are dealt duirng Game._start() initial deal. Method is a
        stub for a subclass to deal cards beyond the initial deal.

        :keyword boolean sound: sound fx?
        """
        pass

    def removeAllCards(self):
        """
        removeAllCards() -> None

        Remove cards from all other piles.
        """
        for stack in self.game.allstacks:
            while stack.cards:
                stack.removeCard(update=False)
            stack._update_text()

    @state.not_state('is_preview')
    def _update_text(self, update_rounds=True, update_redeal=True):
        """
        _update_text([update_rounds:bool=True, update_redeal:bool=True])
          -> None

        In addition, update pile's labels and redeal base image.

        :cvar object images: update redeal image after any change
        :cvar object texts: update number of rounds amd redeal status

        :keyword boolean update_rounds: update rounds label.
        :keyword boolean update_redeal: update redeal base image.
        """
        super(Talon, self)._update_text()
        texts, images = self.texts, self.images
        if update_rounds:
            if texts['rounds'] is not None:
                texts['rounds'].config(text=_("Round {0}").format(self.round))
        if update_redeal:
            state = int(bool(self.can_deal_cards()))
            if images['redeal'] is not None:
                from ..utillib.cache import IMAGE_CACHE
                image = IMAGE_CACHE.load(['stopsign', 'redeal'][state])
                if image is not None and image is not images['redeal_img']:
                    images['redeal'].config(image=image)
                    images['redeal_img'] = image
                text = ("", _("Redeal"),)[state]
            else:
                text = (_("Stop"), _("Redeal"))[state]
            if texts['redeal']:
               texts['redeal'].config(text=text)

    def prepareView(self):
        """
        prepareView() -> None

        Prepare additional internal pile values for use on the canvas.

        :cvar object init_redeal.top_bottom: set image to top_bottom.
        :cvar object init_redeal.img_coord: set coords for redeal image.
        :cvar object init_redeal.txt_coord: set coords for redeal text.
        """
        super(Talon, self).prepareView()
        self.init_redeal['top_bottom'] = self.top_bottom
        cardw, cardh = self.game.images.size
        cx, cy = self.x + cardw / 2, self.y + cardh / 2
        self.init_redeal['img_coord'] = cx, cy
        self.init_redeal['txt_coord'] = cx, self.y + cardh - 4

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype: base_string
        """
        r = self.max_rounds - 1
        nredeals = {-3: _('Variable redeals.'), -2: _('Unlimited redeals.'),
                     0: _('No redeals.')}.get(r, p_.ngettext('{0} redeal',
                                                  '{0} redeals', r).format(r))
        text = _('Talon {0}.').format(nredeals)
        ##text += " {0}".format(_('Round #{0}.').format(self.round))
        return text

    def resize(self, xf, yf):
        """
        resize(xf, yf) -> None

        Prior also initialize images['redeal']  and text['redeal'].

        :cvar object image.redeal: Show image if size is 60 or more.
        :cvar object image.redeal: Show text below if size is 90 or more.
          Show text center if size is 60 or less

        :param integer xf: a muliple transform factor from original size.
        :param integer yf: a muliple transform factor from original size.
        """
        if (self.game.is_preview or not self.is_visible or
            self.images['bottom'] is None):
            return
        if self.images['redeal']:
            self.canvas.delete(self.images['redeal'])
            self.images['redeal'] = None
        if self.texts['redeal']:
            self.canvas.delete(self.texts['redeal'])
            self.texts['redeal'] = None
        Logger.info('resize (%s, %s)', xf, yf)
        self.top_bottom = self.init_redeal['top_bottom']
        cw, ch = self.game.images.size
        cx, cy = self.init_redeal['img_coord']
        ca = 'center'
        tx, ty = self.init_redeal['txt_coord']
        redeal = self.max_rounds != 1
        Logger.info('redeal: (%s) size (%s, %s)', redeal, cw, ch)
        if cw >= 60 and ch >= 60:
            from ..utillib.cache import IMAGE_CACHE
            image = IMAGE_CACHE.load(['stopsign', 'redeal'][redeal])
            if image is not None:
                Logger.info('add image (%s, %s)', cx, cy)
                self.images['redeal'] = canvas.ImageItem(self.canvas,
                                                 misc.Point(cx, cy),
                                                 image=image, anchor=ca,
                                                 group=self.group)
                self.top_bottom = self.images['redeal']
                self._repositionItems(self.images['redeal'], xf, yf)
                if ch >= 90:
                    cy, ca = ty, "s"
                else:
                    ca = None
        text_width = canvas.TextItem.get_text_width(_('Redeal'),
                                    fontname='default', root=self.canvas)
        if cw >= text_width + 4 and ca and redeal:
            Logger.info('add text (%s, %s)', cx, cy)
            self.texts['redeal'] = canvas.TextItem(self.canvas, (cx, cy), anchor=ca,
                         text=_('Redeal'), fontname='default', group=self.group)
            self.canvas.tag_raise(self.texts['redeal'], above_this=self.top_bottom)
            self.top_bottom = self.texts['redeal']
        super(Talon, self).resize(xf, yf)
        for key in (k for k in ('rounds', 'redeal') if self.texts.get(k)):
            self._repositionItems(self.texts[key], xf, yf)


class DealRowTalon(Talon):
    """
    Pile support for dealing cards to other piles. A <left-click> deals
    cards to each rows after inital deal.

    :ivar string _BOTTOM_IMAGE: use shade bottom image.
    """

    @property
    def _piles(self):
        """
        _piles -> tup

        :return: deal Game.s.rows
        :rtype: tuple
        """
        return self.game.s['rows']

    def deal_cards(self, **kw):
        """
        deal_cards([sound:bool=False]) -> None

        Actual dealing, called from Game.talon_deal(). Overides default,
        deal as many piles of rows num_deal sets as have cards.

        :keyword boolean sound: sound fx?

        note:: modified to use num_deal to deal number of card sets as found
          in some solitaire games.
        """
        ncards = 0
        for i in range(self.num_deal):
            ncards += self.dealRowAvail(rows=self._piles, **kw)
        return ncards


class DealReserveTalon(Talon):
    """
    Pile support for dealing cards to other piles. A <left-click> deals
    cards to each rows after inital deal.

    :ivar string _BOTTOM_IMAGE: use shade bottom image.
    """

    @property
    def _piles(self):
        """
        _piles -> tup

        :return: deal Game.s.reserves
        :rtype: tuple
        """
        return self.game.s['reserves']


class InitialDealTalon(Talon):
    """
    Pile support single inital dealing to other piles. Pile
    lacks interactive bindings with mouse and keyboard.

    :ivar string _BOTTOM_IMAGE_: use none bottom image.
    """
    _BOTTOM_IMAGE_ = None

    def prepareStack(self):
        """
        prepareStack() -> None

        Overide pile preparation for the canvas to omit initbindings()
        """
        self.prepareView()


class RedealTalon(Redeal_cards_StackMethods, Talon):
    """
    Pile support dealing and redealing cards to other piles. A <left-click>
    clears rows and redeals according to Game.talon_redeal().

    :ivar string _BOTTOM_IMAGE: use shade bottom image.
    """

    def can_deal_cards(self):
        """
        can_deal_cards() -> bool

        Usually informs via Game.can_deal_cards() if pile can deal more cards.
        Override default to allow dealing while not max_rounds or
        game.finished().

        :return: deal more cards?
        :rtype: True | False
        """
        return not (self.round == self.max_rounds or self.game.is_complete)

    def deal_cards(self, **kw):
        """
        deal_cards([sound:bool=False]) -> None

        Actual dealing, called from Game.talon_deal(). Overides default,
        move cards back from and then redeal cards to Game.s.rows group.

        :keyword boolean sound: sound fx?
        """
        self.redeal_cards(**kw)


class DealRowRedealTalon(RedealTalon):
    """
    Pile support dealing redealing sets of cards to Game.s.rows. A
    <left-click> deals num_deal sets of cards on rows and when empty clears
    rows.

    :ivar string _BOTTOM_IMAGE: use shade bottom image.
    """

    @property
    def _piles(self):
        """
        _piles -> tup

        :return: deal Game.s.rows
        :rtype: tuple
        """
        return self.game.s['rows']

    @property
    def _pilesHaveCards(self):
        """
        _pilesHaveCards -> int

        :return: Game.s.rows has cards.
        :rtype: integer
        """
        return any(r.cards for r in self._piles)

    def can_deal_cards(self, **kw):
        """
        can_deal_cards() -> bool

        Usually informs via Game.can_deal_cards() if pile can deal more cards.
        In addition, ensure either pile or piles being dealt has cards.

        :return: deal more cards?
        :rtype: True | False

        important:: method relies on _piles property to set group being dealt.
        """
        return (self.cards or self._pilesHaveCards
                or super(RedealTalon, self).can_deal_cards())

    def deal_cards(self, rows=None, shuffle=False, sound=False, reverse=False,
                  **kw):
        """
        deal_cards([sound:bool=False, shuffle=False, reverse=False]) -> int

        Actual dealing, called from game.v(). Overide default, to
        allow cards to be dealt a set to and redealt from _piles.

        :keyword boolean sound: sound fx?
        :keyword boolean shuffle: shuffle cards before redealing?
        :keyword boolean reverse: deals cards in reverse order?
        :return: number of cards dealt.
        """
        ncards = 0
        game = self.game
        if sound and game.can_play_sound:
            game._play_sound('deal')
        if not self.cards:
            ncards += self._redeal(rows=self._piles, frames=4)
            if shuffle:
                game._move_do(a_move.AShuffleStackMove(self, game.random)
                              )  #low level
            if reverse:
                self.cards.reverse()
            game._move_do(a_move.ANextRoundMove(self), reset=True)  # low level
        for i in range(self.num_deal):
            ncards += self.dealRowAvail(rows=self._piles, **kw)
        return ncards


class DealReserveRedealTalon(DealRowRedealTalon):
    """
    Pile support dealing redealing sets of cards to Game.s.reserves. A
    <left-click> deals num_deal sets of cards on rows and when empty clears
    rows.

    :ivar string _BOTTOM_IMAGE: use shade bottom image.
    """

    @property
    def _piles(self):
        """
        _piles -> seq

        :return: deal Game.s.reserves
        :rtype: sequence
        """
        return self.game.s['reserves']


class RedealShuffleTalon(RedealTalon):
    """
    Deal Game.redeal_cards() after reset and shuffle of _row_.
    """

    def deal_cards(self, **kw):
        """
        deal_cards([sound:bool=False]) -> None

        Shuffle before redeal _row_.
        """
        kw.setdefault('shuffle', True)
        self.redeal_cards(**kw)


class SpiderTalon(DealRowRedealTalon):
    """
    Pile support dealing redealing sets of cards to Game.s.rows. A
    <left-click> deals num_deal sets of cards on rows while none are empty and
    when empty clears rows.

    :ivar string _BOTTOM_IMAGE: use shade bottom image.
    """

    def can_deal_cards(self):
        """
        can_deal_cards() -> bool

        Usually informs via Game.can_deal_cards() if pile can deal more
        cards. In addition, make sure no pile is a gap.

        :return: deal more cards?
        :rtype: True | False
        """
        return (super(SpiderTalon, self).can_deal_cards() and
                all(r.cards for r in self._piles))


class GroundsForADivorceTalon(DealRowRedealTalon):
    """
    Pile support dealing redealing sets of cards to Game.s.rows. A
    <left-click> deals num_deal sets of cards on rows with cards and when
    empty clears rows.

    :ivar string _BOTTOM_IMAGE: use shade bottom image.
    """

    @property
    def _piles(self):
        """
        _piles -> seq

        :return: deal parent group if cards else single card on first.
        :rtype: sequence
        """
        piles = super(GroundsForADivorceTalon, self)._piles()
        rows = [r for r in piles if r.cards]
        return rows or piles[0]


class FechtersTalon(DealRowTalon):
    """
    Pile support dealing dealing sets of cards to Game.s.rows. A
    <left-click> deals num_deal sets of cards on rows without base KING seq.

    :ivar string _BOTTOM_IMAGE: use shade bottom image.
    """

    @property
    def _piles(self):
        """
        _piles -> tup

        :return: deal rows with base KING seq
        :rtype: tuple
        """
        rows = []
        for r in self.game.s['rows']:
            for i in range(1, len(r.cards) + 1):
                cards = r.cards[-i:]
                if r._sequence(cards) and cards[0]['rank'] == KING:
                    break
            else:
                rows += r
        return rows


class OpenDragMethods(object):

    def releaseHandler(self, event, drag, **kw):
        """
        releaseHandler(event:obj, drag:obj[, sound:bool=True]) -> None

        Confirm drag pile has moved off its pile of origin. When it has locate
        the closest pile and confirm its acceptance. Otherwise return drag
        pile to pile of origin.

        :param object drag: current drag pile
        :param object stack: recieving pile.
        :param boolean sound: sound fx?
        """
        cards = drag['cards']
        if event is not None:
            dx, dy = event.x - drag['start_x'], event.y - drag['start_y']
            if abs(dx) < 10 and abs(dy) < 10:  # move cards back
                Logger.info("delta (%d, %d), moved back", dx, dy)
                super(OpenDragMethods, self).releaseHandler(event, drag, **kw)
                return
        if CONF_OPT['mouse_type'] == 'point-n-click':
            from_stack = drag['stack']
            to_stack = self
        else:
            from_stack = self
            to_stack = self.game.get_closest_stack(cards[0], self)
        if (not to_stack or from_stack is to_stack or
            not to_stack.cardsBuild(from_stack, cards)):  # move cards back
            Logger.info("delta (%d, %d), to_stack (%s)", dx, dy, from_stack)
            super(OpenDragMethods, self).releaseHandler(event, drag, **kw)
        else:
            Logger.info("delta (%d, %d), from_stack (%s)", dx, dy, to_stack)
            from_stack.dragMove(drag, to_stack, **kw)


class OpenAutoMethods(object):

    def rightclickHandler(self, event):
        """
        rightclickHandler(event:obj) -> bool

        Treat <right-click> as <double-click> by calling doubleclickHandler()
        and if returns False | None call a handle_quickplay() for *quickplay*.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        if self.doubleclickHandler(event):
            return True
        return self.game.quick_play(self, event)

    def doubleclickHandler(self, event):
        """
        doubleclickHandler(event:obj) -> bool

        A <double-click> will flip or drop a card. Any flip move or failed
        drop move will continue event with a new drag move. Any successful
        drop move will end event.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        flip, drop = self.game._autopiles(event)[:2]
        if self in flip and self.cardFlips():
            self.game.flip(self)
            return None
        if self in drop:
            to_stack, ncards = self.cardsDrop(self.game.s['foundations'],
                                                 event=event)
            if to_stack:
                self.game.move(ncards, self, to_stack)
                return True
        return False


class OpenStack(OpenDragMethods, OpenAutoMethods, Stack):
    """
    Pile support for builiding and playing cards.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 0), *capability*
          {max_move:int=1}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        cap.setdefault('max_move', 1)
        super(OpenStack, self).__init__(x, y, game, **cap)

    # Capability
    def cardsBlock(self):
        """
        cardsBlock() -> bool

        Check if pile is blocked. The default is False making cards able to
        build or play. Subclass method to change blocking.

        :return: pile is blocked?
        :rtype: boolean
        """
        return False

    # Mouse handlers
    def clickHandler(self, event):
        """
        clickHandler(event:obj) -> bool

        A <left-click> will flip over top card if pile in autostacks,
        flipstacks, and cardFlips().

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        if self in self.game._autopiles(event)[0] and self.cardFlips():
            self.game.flip(self)
            return True
        return False

    def controlclickHandler(self, event):
        """
        controlclickHandler(event:obj) -> bool

        A <control-click will> highlight matching cards.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        if CONF_OPT['mark_cards']:
            self.highlightMatchingCards(event)
            return True
        return False

    def dragMove(self, drag, stack, **kwargs):
        """
        dragMove(drag, stack[, sound=True, frames=-1, shadows=-1]) -> None

        :Parameters:
            drag : game.Game.DragStack
              current drag pile
            stack : stack.Stack
              recieving pile.

        :Keywords:
            sound : bool
              sound fx?
        """
        if CONF_OPT['mouse_type'] == 'point-n-click':
            kwargs.default('frames', -2)

        self.game.move(len(drag['cards']), self, stack, **kwargs)

    def _quick_from_pile(self, piles=None, event=None):
        """
        Quickplay: Return what piles sequence plays on self by score.

        .. \_quick_from_pile([piles:seq(Pile ...), event:obj=None) -> list

        *Parameters:*
          piles: {seq(Piles ...)}
            piles to play on self.
          event: {Event} None
        """
        game = self.game
        piles = piles or game.sg['dropstacks']
        Logger.info("play from piles (%s)", piles)
        move = {}
        for pile in piles:
            if pile is not self and pile.cards:
                seq = pile.SEQUENCE
                if seq and self.cardsBuild(seq, pile):
                    arg = [len(seq), pile, self]
                    score = game._quickplay_(*arg)
                    moves[score * len(moves)] = arg
        return moves

    def _quick_to_pile(self, piles=None, event=None):
        """
        Quickplay: Return what sequence builds on piles by score.

        .. \_quick_to_pile([piles:seq(Pile ...), event:obj=None) -> list

        *Parameters:*
          piles: {seq(Piles ...)}
            piles to build from self.
          event: {Event} None
        """
        game = self.game
        piles = piles or list(game.s['foundations']) + list(game.sg['dropstacks'])
        Logger.info("stacks (%s)", piles)
        moves = {}
        if self.SEQUENCE:
            index = self._locate(event)
            if index is not None and index >= 0:
                seq = self.cards[index:]
                if not self.cardsPlay(seq):
                    seq = self.SEQUENCE
                for pile in piles:
                    if pile is not self and pile.cardsBuild(self, seq):
                        args = [len(seq), self, pile]
                        score = game._quickplay_(*args)
                        moves[score * len(moves)] = args
        return moves

    def handle_quickplay(self, event, from_stacks=None, to_stacks=None):
        """
        Quick play. Move best sequence to a pile or sequence on self.

        *note:*
          - Use from_stacks and to_stacks for subclass with different piles.
          - _quick_from_pile and _quick_to_pile methods for subclass with
             different selection criteria.

        .. handle_quickplay(event:obj, *,
             from_stacks:seq(Pile ...)=sg[dropstacks],
             to_stacks:seq(Pile ...)=sg[dropstacks] + s[foundations]) -> None

        *Parameters:*
          event: {Event} None.
          from_stack: seq(Pile ...) sg[dropstacks]
            Build a sequence from a pile. *AS_KEYWORD_ONLY*
          to_stack: seq(Pile ...) sg[dropstacks] + s[foundations]
            Play a sequence to a pile. *AS_KEYWORD_ONLY*
        """
        if not self.cards:
            moves = self._quick_from_pile(from_stacks, event=event)
        else:
            moves = self._quick_to_pile(to_stacks, event=event)
        if moves:
            ncards, from_pile, to_pile = moves[max(moves)]
            Logger.info("move (%s)", (ncards, from_pile, to_pile,))
            assert to_pile.cardsBuild(from_pile, from_pile.cards[-ncards:])
            self.game.move(ncards, from_pile, to_pile)
            return True
        return False

    # Assistance
    def _helpString(self):
        """
        _helpString() -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.NO_BUILD_STR if self.cap['max_accept'] == 0 else ''

    @property
    def _helpString(self):
        """
        _helpString() -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return "{0} {1}".format(_('Reserve.'),
                                super(OpenStack, self)._helpString)


class FinishCloseMethod(object):

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard a shade and is_filled on
        pile with max_cards.
        """
        assert hasattr(self, '_sequence')
        cap = self.cap
        suit = cap['suit'] if cap['suit'] > 0 else self.TOPCARD['suit']
        RANKS = cmp_cards.highestRank(self.game, suit) + 1
        if (len(self.cards) == min(cap['max_cards'], RANKS) and
            self._sequence(self.cards)):
            self.is_filled = True
            self._shadeStack()


class AbstractFoundation(FinishCloseMethod, OpenStack):
    """
    Pile with support for finishing cards from other pile groups.

    :ivar string _BOTTOM_IMAGE_: use a suit bottom image for stack.
    """
    _BOTTOM_IMAGE_ = 'suit'

    def __init__(self, x, y, game, suit, **cap):
        """
        __init__(x:int, y:int, game:obj, suit:int[, offset:tup=(0, 0),
          *capability* {max_accept:int=1, max_cards:int=*RANKS*,
          base_suit:int=suit, base_rank:int=ACE, dir:int=1}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's suit and base_suit
        :keyword tuple offset: offset from left and offset from top

        note:: modified max_cards *capability* to size itself accoring to
          Game['suits'].

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        ranks = cmp_cards.highestRank(game, suit) + 1
        misc.kwdefault(cap, suit=suit, base_suit=suit, base_rank=ACE, dir=1,
                  max_accept=1, max_cards=ranks)
        super(AbstractFoundation, self).__init__(x, y, game, **cap)

    # Capabilities
    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean

        important:: This method should be defined in any subclass.
        """
        return True

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Check parent.cardsBuild() and cards isSequence() with top card
        acccording to *capabilities*.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        if not all([super(AbstractFoundation, self).cardsBuild(
                from_stack, cards), self._sequence(cards)]):
            return False
        return not self.cards or self._sequence(self.cards[-1:] + cards)

    def cardsDrop(self, stacks, event=None):
        """
        cardsDrop(stacks:seq) -> seq

        Overide to disable drop move.

        :param sequence stacks: usually Game.s.foundations
        :return: pile and number of cards.
        :rtype: tuple
        """
        return (None, 0)

    # Handlers
    def clickHandler(self, event):
        """
        clickHandler(event:obj) -> bool

        Override to perform nothing.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        return False

    def rightclickHandler(self, event):
        """
        rightclickHandler(event:obj) -> bool

        A <right-click> performs just a handle_quickplay() *quickplay*.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        return self.game.quick_play(self, event)

    def handle_quickplay(self, event, from_stacks=None, to_stacks=None):
        """
        Quick play. Override to include foundations in from_pile method.

        *note:*
          - Use from_stacks and to_stacks for subclass with different piles.
          - _quick_from_pile and _quick_to_pile methods for subclass with
             different selection criteria.

        .. handle_quickplay(event:obj, *,
             from_stacks:seq(Pile ...)=sg[dropstacks] + s[foundations],
             to_stacks:seq(Pile ...)=sg[dropstacks] + s[foundations]) -> None

        *Parameters:*
          event: {Event} None.
          from_stack: seq(Pile ...) sg[dropstacks] + s[foundations]
            Build a sequence from a pile. *AS_KEYWORD_ONLY*
          to_stack: seq(Pile ...) sg[dropstacks] + s[foundations]
            Play a sequence to a pile. *AS_KEYWORD_ONLY*
        """
        game = self.game
        piles = list(game.sg['dropstacks']) + list(game.s['foundations'])
        return super(AbstractFoundation, self).quickPlayHandler(event,
                                               from_stacks=from_stacks or piles,
                                               to_stacks=to_stacks or piles)

    # Assistance
    def getBaseCard(self):
        """
        getBaseCard() -> str
        :return: pile base card as string for a canvas label.
        :rtype: str
        """
        return self._getBaseCard()

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return "{0} {1}".format(_('Foundation.'),
                            super(AbstractFoundation, self)._helpString).strip()


class SingleFoundation(AbstractFoundation):
    """
    Pile with support for finishing cards from other pile groups.

    :ivar string _BOTTOM_IMAGE_: use a suit bottom image for stack.
    """
    _BOTTOM_IMAGE_ = 'suit'

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 0), *capability*
          {max_accept:int=0, max_move:int=0, max_cards:int=*CARDS*,
          suit:int=ANY, base_suit:int=ANY, base_rank:int=ANY, dir:int=0}]) ->
          None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's suit and base_suit
        :keyword tuple offset: offset from left and offset from top

        note:: modified max_cards *capability* to size itself accoring to
          Game['ncards'].

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        cards = game['ncards']
        misc.kwdefault(cap, suit=ANY, base_suit=ANY, base_rank=ANY,
                  max_accept=0, max_move=0, max_cards=cards)
        super(SingleFoundation, self).__init__(x, y, game, **cap)


class SS_Foundation(AbstractFoundation):
    """
    Pile support finishing cards from other pile groups and builds up in rank
    order by same suit. This pile is the foundation pile found in most games.

    :ivar string _BOTTOM_IMAGE_: use a suit bottom image for stack.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence?
        :rtype: boolean
        """
        return cmp_cards.isRankSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_SS_DICT.get(hs.dir_(self.cap['dir']), '')


class RK_Foundation(SS_Foundation):
    """
    Pile support finishing cards from other pile groups and builds up in rank
    order alone. This pile is common in Numerica Type games.

    :ivar string _BOTTOM_IMAGE_: use a suit bottom image for stack.
    """

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 0),
          *capability*{suit:int=ANY}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's suit and base_suit
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        cap['suit'] = ANY
        super(RK_Foundation, self).__init__(x, y, game, **cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_RK_DICT.get(hs.dir_(self.cap['dir']), '')


class AC_Foundation(RK_Foundation):
    """
    Pile support finishing cards from other pile groups and builds up in rank
    order by alternate color.

    :ivar string _BOTTOM_IMAGE_: use a suit bottom image for stack.
    """

    def __init__(self, x, y, game, suit, **cap):
        """
        __init__(x:int, y:int, game:obj, suit:int[, offset:tup=(0, 0),
          *capability* {base_suit:int=suit}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's base_suit
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        cap.setdefault('base_suit', suit)
        super(AC_Foundation, self).__init__(x, y, game, **cap)

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence?
        :rtype: boolean
        """
        return cmp_cards.isAlternateColorSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_AC_DICT.get(hs.dir_(self.cap['dir']), '')


class SC_Foundation(AC_Foundation):
    """
    Pile support finishing cards from other pile groups and builds up in rank
    order by same color.

    :ivar string _BOTTOM_IMAGE_: use a suit bottom image for stack.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence?
        :rtype: boolean
        """
        return cmp_cards.isSameColorSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_SC_DICT.get(hs.dir_(self.cap['dir']), '')


class UD_RK_Foundation(AbstractFoundation):

    def isSequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence?
        :rtype: boolean
        """
        return cmp_cards.isEitherRankSequence(cards, **self.cap['mod'])

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.EITHER_RK_STR


class Vary_FoundationMethods(object):
    """
    Pile with support for finishing cards from other pile groups. This is the
    Foundation pile has its base_rank set by first card on group which then
    builds up in rank order by same suit.

    :ivar string _BOTTOM_IMAGE_: use a suit bottom image for stack.
    """

    def __init__(self, x, y, game, suit, **cap):
        """
        __init__(x:int, y:int, game:obj, suit:int[, offset:tup=(0, 0),
          *capability* {suit:int=suit, base_rank:int=ANY, min_cards=1,
          mod=13}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's suit and base_suit
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        misc.kwdefault(cap, suit=suit, base_rank=ANY, min_cards=1, mod=13)
        super(Vary_FoundationMethods, self).__init__(x, y, game, **cap)

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Sets a base_rank for pile group when ANY, then checks
        parent.cardsBuild().

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        if self.cap['base_rank'] == ANY:
            self.game._save_stack_caps(self.game.s['foundations'],
                                       base_rank=cards[0]['rank'])
        return super(Vary_FoundationMethods, self).cardsBuild(from_stack, cards)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        build = super(Vary_FoundationMethods, self)._helpString
        return "{0} {1}".format(build, hs.FIRST_RANK_STR).strip()


class Vary_SS_Foundation(Vary_FoundationMethods, SS_Foundation):
    pass


class Vary_AC_Foundation(Vary_FoundationMethods, AC_Foundation):
    pass


class Vary_RK_Foundation(Vary_FoundationMethods, RK_Foundation):
    pass


class SequenceStack_StackMethods(object):
    """
    Supports concept of building and playing sequences of cards between
    different piles.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:tup) -> bool

        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean

        important:: Method must be defined by any subclass.

        note:: This method allows subclass to override the accepted and
          moeveable sequence.
        """
        raise NotImplemented

    def _build_sequence(self, cards):
        """
        _build_sequence(cards:tup) -> bool

        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean

        note:: This method allows subclass to override the accepted sequence,
          while allowing _play_sequence() to use _sequence() method.
        """
        return self._sequence(cards)

    def _play_sequence(self, cards):
        """
        _play_sequence(cards:tup) -> bool

        :param tuple cards: cards to move from this pile
        :return: cards moveable?
        :rtype: boolean

        note:: This method allows subclass to override the moveable sequence,
          while allowing _build_sequence() to use _sequence() method.
        """
        return self._sequence(cards)

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Checks *capability* for accepting cards and _build_sequence().

        :param object from_stack: another pile for subclass usage
        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean
        """
        if not (super(SequenceStack_StackMethods, self).cardsBuild(
                from_stack, cards) and self._build_sequence(cards)):
            return False
        return not self.cards or self._build_sequence(self.cards[-1:] +
                                                            cards)

    def cardsPlay(self, cards):
        """
        cardsPlay(cards:tup) -> bool

        Checks *capability* for moving cards and _play_sequence().

        :param tuple cards: cards to move from this pile
        :return: cards moveable?
        :rtype: boolean
        """
        return (super(SequenceStack_StackMethods, self).cardsPlay(cards)
                and self._play_sequence(cards))


class BasicRowStack(OpenStack):
    """
    OpenStack with a vertical offset which can build down from any rank.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.

    note:: Needs subclasses to determine acceptable and buildable sequences.
      Also max_accept needs to be greater than its default of 0.
    """

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, YOFFSET),
          *capability* {dir:int=-1}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        misc.kwdefault(cap, offset=(0, game.images.offset.yoffset), dir=-1)
        super(BasicRowStack, self).__init__(x, y, game, **cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return "{0} {1}".format(_('Tableau.'),
                                super(BasicRowStack, self)._helpString)


class SequenceRowStack(SequenceStack_StackMethods, BasicRowStack):
    """
    Pile which  playing and building sequences.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game[, offset:tuple=(0, YOFFSET), *capability*
          {max_move:int=MAX, max_accept:int=MAX}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        misc.kwdefault(cap, max_move=MAX, max_accept=MAX)
        super(SequenceRowStack, self).__init__(x, y, game, **cap)

    # Assistance
    def getBaseCard(self):
        """
        getBaseCard() -> str

        :return: pile base card as string for a canvas label.
        :rtype: str
        """
        return self._getBaseCard()


class AC_RowStack(SequenceRowStack):
    """
    Pile which supports playing and building sequences. The main playing piles
    which build down in rank order by alternate color.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in rank order by alternate color.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isAlternateColorSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_AC_DICT.get(hs.dir_(self.cap['dir']), '')


class SC_RowStack(SequenceRowStack):
    """
    Pile which supports playing and building sequences. The main playing piles
    which build down in rank order by same color.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in rank order by same color.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isSameColorSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_SC_DICT.get(hs.dir_(self.cap['dir']), '')


class SS_RowStack(SequenceRowStack):
    """
    Pile which supports playing and building sequences. The main playing piles
    which build down in rank order by same suit.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in rank order by same suit.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isSameSuitSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_SS_DICT.get(hs.dir_(self.cap['dir']), '')


class RK_RowStack(SequenceRowStack):
    """
    Pile which supports playing and building sequences. The main playing piles
    which build down in rank order alone.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in rank order.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isRankSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_RK_DICT.get(hs.dir_(self.cap['dir']), '')


class BO_RowStack(SequenceRowStack):
    """
    Pile which supports playing and building sequences. The main playing piles
    which build down in rank order by alternate suit.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in rank order by alternate suit.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isAnySuitButOwnSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_BO_DICT.get(hs.dir_(self.cap['dir']), '')


class AF_RowStack(SequenceRowStack):
    """
    Pile which supports playing and building sequences. The main playing piles
    which build down in rank order by alternate force.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in rank order by alternate force.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isAlternateForceSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_AF_DICT.get(hs.dir_(self.cap['dir']), '')


class SF_RowStack(SequenceRowStack):
    """
    Pile which supports playing and building sequences. The main playing piles
    which build down in rank order by |=force|.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    def _sequence(self, cards):
        """
        .. method:: _sequence(cards:obj) -> bool

        Are ``cards`` are in rank order by |=force|.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        return cmp_cards.isSameForceSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_SF_DICT.get(hs.dir_(self.cap['dir']), '')


class SingleAsDoubleClickMethod(object):

    def clickHandler(self, event):
        """
        clickHandler(event:obj) -> bool

        Override to perform <double-click>.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        self.doubleclickHandler(event)


class GapFromBuildMethod(object):

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent can build with cards from from_stack. Card must be from
        _is_fromstacks when Gap.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean

        seealso:
          Method pysollib.stackcaps.builds for the *capability* check.
        """
        if not hasattr(self._fromstacks):
            raise NotImplemented
        return ((self.cards or from_stack not in self._fromstacks) and
                super(GapFromBuildMethod, self).cardsBuild(from_stack, cards))


class GapFromWasteBuildMethod(GapFromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Only cards from Game.s.waste piles may build when gap.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['wastes']


class GapNotFromBuildMethod(object):

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent can build with cards from from_stack. Card must be from
        _is_fromstacks when Gap.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean

        seealso:
          Method pysollib.stackcaps.builds for the *capability* check.
        """
        if not hasattr(self._fromstacks):
            raise NotImplemented
        return ((self.cards or from_stack not in self._fromstacks) and
                super(GapFromBuildMethod, self).cardsBuild(from_stack, cards))


class GapFromTalonBuildMethod(object):

    @property
    def _fromstacks(self):
        """
        Only cards from Game.s.talon piles may build when gap.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return [self.game.s['talon']]


class GapFromReserveTalonBuildMethod(object):

    @property
    def _fromstacks(self):
        """
        Only cards from Game.s.reserves or Game.s.talon piles may build when
        gap.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['reserves'] + [self.game.s['talon']]


class GapFromReserveBuildMethod(object):

    @property
    def _fromstacks(self):
        """
        Only cards from Game.s.reserves piles may build when gap.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['reserves']


class EmptyTalonBuildMethod(object):

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Also check Game.sg.Talons are gaps before building.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        return (not all(pile.cards for pile in self.game.sg['Talons']) and
                super(EmptyTalonBuildMethod, self).cardsBuild(from_stack,
                                                              cards))


class EmptyTalonPlayMethod(object):

    def cardsPlay(self, from_stack, cards):
        """
        cardsPlay(from_stack:obj, cards:seq) -> bool

        Also check Game.sg.Talons are gaps before play.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        return (not self.game.s['talon'].cards and
                super(EmptyTalonPlayMethod, self).cardsPlay(from_stack,
                                                              cards))

    def clickHandler(self, event):
        """
        clickHandler(event:obj) -> bool

        Override <left-click> to deal a card to the pile while talon has cards
        then when talon has no cards flip over top card if pile in autostacks,
        flipstacks, and cardFlips().

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        if self.game.s['talon'].cards:
            self.game.move(1, self.game.s['talon'], self)
            return True
        return super(EmptyTalonPlayMethod, self).clickHandler(event)

    def doubleclickHandler(self, event):
        """
        doubleclickHandler(event:obj) -> bool

        Override <double-click> to deal a card to the pile while talon has
        cards then when talon has no cards flip or drop a card. Any flip move
        or failed drop move will continue event with a new drag move. Any
        successful drop move will end event.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        if self.game['talon'].cards:
            self.game.play_pile__move(1, self.game['talon'], self)
            return True
        return super(EmptyTalonPlayMethod, self).doubleclickHandler(event)


class GapTopBuildMethod(object):

    def cardsBuild(self, from_stack, cards):
        """
        Check cards can build. Build 1 card if gap else caps.max_accept cards.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return (super(GapTopBuildMethod, self).cardsBuild(from_stack, cards)
                and (self.cards or len(cards) == 1))


class NotFromBuildMethod(object):

    def cardsBuild(self, from_stack, cards):
        """
        Check cards can build. Cards may not build from _isNotInFromStacks.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        if not hasattr(self, '_fromstacks'):
            raise NotImplemented
        return (not from_stack in self._fromstacks and
                super(NotFromBuildMethod, self).cardsBuild(from_stack,
                                                                  cards))


class NotFromRowStackBuildMethod(NotFromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards may not be played from Game.s.rows.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['rows']


class NotFromReserveBuildMethod(NotFromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards may not be played from Game.s.rows.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['reserves']


class NotFromTalonBuildMethod(NotFromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards may not be played from Game.s.rows.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return [self.game.s['talon']]


class FromBuildMethod(object):

    def cardsBuild(self, from_stack, cards):
        """
        Check cards can build. Cards may not build from _isNotInFromStacks.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        if not hasattr(self, '_fromstacks'):
            raise NotImplemented
        return (from_stack in self._fromstacks or
                super(FromBuildMethod, self).cardsBuild(from_stack,
                                                                  cards))


class FromWasteBuildMethod(FromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards must be played from Game.s.waste.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['wastes']


class FromTalonWasteBuildMethod(FromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards must be played from Game.s.waste.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['wastes'] + [self.game.s['talon']]


class FromTalonBuildMethod(FromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards must be played from Game.s.talon.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return [self.game.s['talon']]


class FromRowStackWasteBuildMethod(FromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards must be played from Game.s.waste or Game.s.rows.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['wastes'] + self.game.s['rows']


class FromRowStackTalonBuildMethod(FromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards must be played from Game.s.waste or Game.s.rows.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return [self.game.s['talon']] + self.game.s['rows']


class FromReserveBuildMethod(FromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards must be played from Game.s.waste or Game.s.rows.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return self.game.s['reserves']


class FromRowStackBuildMethod(FromBuildMethod):

    @property
    def _fromstacks(self):
        """
        Piles cards must be played from Game.s.rows.

        :param object from_stack: pile that held sequence.
        :param sequence cards: sequence.
        :return: sequence accepted?
        :rtype: boolean
        """
        return  self.game.s['rows']


class FirstFinishMethod(object):
    """
    Foundation must have at least 1 card before build.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check if a _foundation_ pile has a card and parent can build with
        cards from from_stack.

        :Parameters:
          from_stack : stack.Stack
            pile playing cards.
          cards : tuple
            cards playing on pile.
        :returns: pile can build with cards from played from_stack?

        seealso:
          Method pysollib.stackcaps.builds for the *capability* check.
        """
        return (self.game.s['foundations'][0].cap['base_rank'] != ANY and
                super(FirstFinishMethod, self).cardsBuild(from_stack,
                                                                cards))

    def move_pile(self, ncards, to_stack, **kw):
        """
        move_pile(ncards:int, to_stack:obj[, frames:int=-1, shadow:int=-1])
          -> None

        Overide move_pile to finish base card on _foundation_ before any play.

        :Parameters:
          ncards : int
            move card at ncards deep in pile.
          to_stack : stack.Stack
            receiving pile.
        """
        if not self.game.s['foundations'][0].caps['baserank'] == ANY:
            super(FirstFinishMethod, self).move_pile(ncards, to_stack, **kw)
        else:
            s = self.game.s
            assert to_stack in s.foundations
            super(FirstFinishMethod, self).move_pile(ncards, to_stack, **kw)


class UpDown_StackMethods(object):
    """
    Supports sequences that build up and down.
    """

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[offset:tup=(0, YOFFSET),
          *capability*{max_move=1, max_accept=1}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        misc.kwdefault(cap, max_move=1, max_accept=1)
        super(UpDown_StackMethods, self).__init__(x, y, game, **cap)

    def _sequence(self, cards):
        """
        _sequence(cards:tup) -> bool

        Wrapper for parent _sequence() with dir *capability* set both
        negative and positive.

        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean
        """
        check_cards = cards[:]
        if super(UpDown_StackMethods, self)._sequence(cards):
            return True
        check_cards.reverse()
        return super(UpDown_StackMethods, self)._sequence(cards)


class UD_SC_RowStack(UpDown_StackMethods, SC_RowStack):
    """
    Pile supports playing and building sequences in both directions. The
    playing piles build in rank order by same color.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.EITHER_SC_STR


class UD_AC_RowStack(UpDown_StackMethods, AC_RowStack):
    """
    Pile supports playing and building sequences in both directions. The
    playing piles build in rank order by alternate color.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.EITHER_AC_STR


class UD_SS_RowStack(UpDown_StackMethods, SS_RowStack):
    """
    Pile supports playing and building sequences in both directions. The
    playing piles build in rank order by same suit.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.EITHER_SS_STR


class UD_RK_RowStack(SequenceRowStack):
    """
    Pile supports playing and building sequences in both directions. The
    playing piles build in rank order by same suit.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.EITHER_RK_STR


class SuperMove_StackMethods(object):
    """
    Supports consideration of the number of free rows and cells in determining
    number of moveable cards.

    seealso:: _SuperMove section_ in the _FreeCell FAQ_.
    """

    # Capabilities
    def _getMaxMove(self, ncards):
        """
        _getMaxMoves(ncards:int) -> int

        Consider number of free rows and cells available to determine maximum
        moveable cards.

        :param integer ncards: number of cards proposed.
        :return: max number moveable cards possible.
        :rtype: integer
        """
        s = self.game.s
        max_move = len([p for p in s.reserves if not p.cards]) + 1
        if self.cap['base_rank'] != ANY:
            return max_move
        n = len([p for p in s.rows if not p.cards])
        n -= 1 if ncards == 0 else 0
        max_move = max_move * (2 ** n)
        return max_move

    def _getNumSSSeq(self, cards):
        """
        _getNumSSSeq(ncards:int) -> int

        Get number of _sequence() in cards.

        :param sequence cards: cards proposed.
        :return: number of sequence in cards.
        :rtype: integer

        .. note:: Used for SuperMoveSpider_RowStack.
        """
        if not cards:
            return 0
        n = 1
        card = cards[-1::]
        for c in cards[-2::-1]:
            if not self._sequence(card + [c]):
                card = c
                n += 1
        return n

    def _play_sequence(self, cards):
        """
        _build_sequence(cards:tup) -> bool

        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean

        note:: This method allows subclass to override the accepted sequence,
          while allowing _play_sequence() to use _sequence() method.
        """
        return (self._sequence(cards) and
                len(cards) <= self._getMaxMoves(ncards=1))

    def _build_sequence(self, cards):
        """
        _play_sequence(cards:tup) -> bool

        :param tuple cards: cards to move from this pile
        :return: cards moveable?
        :rtype: boolean

        note:: This method allows subclass to override the moveable sequence,
          while allowing _build_sequence() to use _sequence() method.
        """
        return (self._sequence(cards) and
                len(cards) <= self._getMaxMoves(ncards=len(self.cards)))

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.SUPERMOVE_STR


class SuperMoveSS_RowStack(SuperMove_StackMethods, SS_RowStack):
    """
    Pile which supports playing and building sequences with a restriction of
    moves to number of available rows and cells plus one. The playing piles
    build down in rank order by same suit.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """
    pass


class SuperMoveAC_RowStack(SuperMove_StackMethods, AC_RowStack):
    """
    Pile which supports playing and building sequences with a restriction of
    moves to number of available rows and cells plus one. The playing piles
    build down in rank order by alternate color.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """
    pass


class SuperMoveRK_RowStack(SuperMove_StackMethods, RK_RowStack):
    """
    Pile which supports playing and building sequences with a restriction of
    moves to number of available rows and cells plus one. The playing piles
    build down in rank order alone.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """
    pass


class SuperMoveSC_RowStack(SuperMove_StackMethods, SC_RowStack):
    """
    Pile supports playing and building sequences with a restriction of moves
    to number of available rows and cells plus one. The playing piles build
    down in rank order by same color.

    :ivar string _BOTTOM_IMAGE_: use a blank bottom image for stack.
    """
    pass


class SuperMoveBO_RowStack(SuperMove_StackMethods, BO_RowStack):
    """
    Pile supports playing and building sequences with a restriction of moves
    to number of available rows and cells plus one. The playing piles build
    down in rank order by alternate suit.

    :ivar string _BOTTOM_IMAGE_: use a shade bottom image for stack.
    """
    pass


class WasteStack(OpenStack):
    """
    Pile supports the holding of cards dealt from the talon pile.

    :ivar string _BOTTOM_IMAGE_: use a none bottom image for stack.
    """

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype: base_string
        """
        ncards = self.cap['max_cards']
        return (p_.ngettext('Accepts {0} card.', 'Accepts {0} cards',
                             ncards).format(ncards)
                             if ncards > MAX and ncards > 0 else '')

    @property
    def _helpString(self):
        """
        _helpString -> str

        :returns: helpful string about pile for its canvas label.
        """
        return "{0} {1}".format(_('Waste.'),
                                super(WasteStack, self)._helpString)


class WasteTalon(Talon):
    """
    Pile supports dealing cards face up to a waste pile for play on other
    piles. A <left-click> deals a card on the waste and when no
    cards available, waste pile is redealt increasing the rounds.

    :ivar string _BOTTOM_IMAGE_: use a shade bottom image for stack.
    """

    def __init__(self, x, y, game, offset=(0, 0), waste=None, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 0),
          waste:obj=Game.s.waste, *talon* {max_rounds:int=1, num_deal:int=1},
          *capability* {}]) -> None

        :cvar object waste: waste

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left top per card on pile.
        :param object waste: pile recieving dealt cards.

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.

          Class pysollib.stack.Talon for *talon* keywords
        """
        super(WasteTalon, self).__init__(x, y, game, offset, **cap)
        self.waste = waste

    def prepareStack(self):
        """
        prepareStack() -> None

        Additional pile preparation for the canvas. Set waste to Game.s.waste
        if not already set.
        """
        super(WasteTalon, self).prepareStack()
        self.waste = self.waste or self.game.s['wastes'][-1]

    def move_pile(self, ncards, to_stack, **kwargs):
        """
        If move to waste flip if not TOPCARD.face_up and move card, else move
        top ncards of pile to_stack then calls fill_stack().

        .. move_pile(ncards:int, to_stack:Pile[, *animation keywords*]) -> None
        """
        if (to_stack is self.waste and not self.TOPCARD.face_up and
            ncards == 1):
            kwargs.setdefault('frames', 4)
            self.game._move_do(a_move.AFlipAndMoveMove(self, self.waste,
                                                       **kwargs),
                                reset=True)  # low-level
            self.fill_stack()
            return
        super(WasteTalon, self).move_pile(ncards, to_stack, **kwargs)

    # Capabilities
    @property
    def num_cards(self):
        """
        cards_to_deal -> int

        :return: number of cards to deal either num_deal or remaining cards.
        :rtype: integer
        """
        return min(len(self.cards), self.num_deal)

    def can_deal_cards(self):
        """
        can_deal_cards() -> bool

        Check by Game.talon_deal() if pile can deal more cards. Default is
        while waste accepts cards or round less than max_rounds.

        :return: deal more cards?
        """
        waste = self.waste
        if self.cards:
            return len(waste.cards) + self.num_cards <= waste.cap['max_cards']
        elif waste.cards and self.round > self.max_rounds:
            return True
        return False

    @state.move_state('deal')
    def deal_cards(self, sound=False, shuffle=False):
        """
        deal_cards([sound=False]) -> None

        Deal cards for Game.talon_deal() to other piles. Deal num_cards to
        waste pile.


        :Keywords:
          sound : bool
            play sound fx?

          shuffle : bool
            reset shuffles `cards`?

        :returns: number of cards dealt
        """
        ncards = 0
        waste = self.waste
        if self.cards:
            ncards = min(len(self.cards), self.num_cards)
            if sound and self.game.can_play_sound:
                self.game._play_sound("dealwaste")
            assert len(waste.cards) + ncards <= waste.cap['max_cards']
            for i in range(ncards):
                self.move_pile(1, waste, frames=4, shadow=0)
        elif waste.cards and self.round != self.max_rounds:
            if sound and self.game.can_play_sound:
                self.game._play_sound("turnwaste", priority=20)
            ncards = len(waste.cards)
            self.game._move_do(a_move.ATurnStackMove(waste, self))  # low lovel
            if shuffle:
                self.game._move_do(a_move.AShuffleStackMove(self, game.random)
                              )  # low level
            self.game._move_do(a_move.ANextRoundMove(self),
                           reset=True)  # low level
        return ncards


class FaceUp_StackMethods(object):
    """
    Pile supports concept of dealing from a face up pile.
    """

    def cardFlips(self):
        """
        cardFlips() -> bool

        :return: flip |top|?
        :rtype: boolean
        """
        return self.cards and not self.TOPCARD.face_up

    def fill_stack(self):
        """
        fill_stack() -> None

        Prior, perfom Game.flip(animation=True) if cardFlips().
        """
        if self.cardFlips():
            self.flip_card()
        self.game._auto_fill(self)


class FaceUpWasteTalon(FaceUp_StackMethods, WasteTalon):
    """
    Pile supports dealing cards from a face up pile to a waste pile for play
    on other piles. A <left-click> deals a card on the waste and when no cards
    available, waste pile is redealt increasing the rounds.

    :ivar string _BOTTOM_IMAGE_: use a shade bottom image for stack.
    """

    def deal_cards(self, sound=False, **kw):
        """
        deal_cards([sound:bool=False, shuffle=False]) -> None

        Actual dealing, called from game.talon_deal(). Overide default, to
        allow cards to be dealt a set to and redealt from ``waste``.

        :keyword boolean sound: sound fx?
        :keyword boolean shuffle: shuffle cards before redealing?
        """
        super(FaceUpWasteTalon, self).deal_cards(**kw)
        if self.cardFlips():
            self.game.flip(self)


class OpenTalon(FaceUp_StackMethods, OpenDragMethods, Talon):
    """
    Pile supports intialing dealing then playing with remaining cards.

    :ivar string _BOTTOM_IMAGE_: use a shade bottom image for stack.
    """

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 0), *talon*
          {max_rounds:int=1, num_deal:int=1}, *capability* {max_move=1}]) ->
          None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left top per card on pile.

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.

          Class pysollib.stack.Talon for *talon* keywords
        """
        cap.setdefault('max_move', 1)
        super(OpenTalon, self).__init__(x, y, game, **cap)

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check if pile can build with cards from from_stack. The default
        prevents any building.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean

        seealso:
          Method pysollib.stackcaps.builds for the *capability* check.
        """
        return False

    def can_deal_cards(self):
        """
        can_deal_cards() -> bool

        Usually informs via game.can_deal_cards() if pile can deal more
        cards. Default is to not allow dealing.

        :return: deal more cards?
        :rtype: True | False
        """
        return False

    def _dealHandler(self, event):
        """
        _dealHandler(event:obj) -> bool

        Handler to allow a subclass easy access to deal a card.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        return super(OpenTalon, self).clickHander(event)

    def _playHandler(self, event):
        """
        _playHandler(event:obj) -> bool

        Handler to allow a subclass easy access to play a card.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        if self in self.game._auto_stacks(event)[0] and self.cardFlips():
            self.game.flip(self)
            return True
        return False

    def clickHandler(self, event):
        """
        clickHandler(event:obj) -> bool

        A <left-click> will deal cards if can_deal_cards(), otherwise will play
        cards.

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        if self.can_deal_cards():
            return self._dealHandler
        return self._playHandler


class OpenAutoTalon(OpenAutoMethods, OpenTalon):
    pass


class ReserveStack(OpenStack):
    """
    Pile supports holding and playing cards. Also known as a FreeCell.

    :ivar string _BOTTOM_IMAGE_: use a shade bottom image for stack.
    """
    _BOTTOM_IMAGE_ = 'shade'

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 0), *capability*
          {max_accept=1, max_cards=1}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        misc.kwdefault(cap, max_accept=1, max_cards=1)
        super(ReserveStack, self).__init__(x, y, game, **cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        pile = _('Reserve.') if self.cap['max_accept'] == 0 else _('FreeCell')
        return "{0} {1}".format(pile, super(ReserveStack, self)._helpString)


class InvisibleStack(Stack):
    """
    Pile supports holding cards off-screen. Pile lacks interactive bindings
    with mouse and keyboard.

    :ivar string _BOTTOM_IMAGE_: use a none bottom image for stack.
    """
    _BOTTOM_IMAGE_ = None

    def __init__(self, game, **cap):
        """
        __init__(game:obj[, offset:tup=(0, 0), *capability*
          {max_accept=1, max_cards=1}]) -> None

        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.

        note::
          x and y coordinates determined canvas get hidden coords
        """
        coords = self.canvas.get_hidden_coords(self.images.size)
        super(InvisibleStack, self).__init__(coords.x, coords.y - 10,
                                             game, **cap)

    def assertStack(self):
        """
        assertStack() -> None

        Additional sanity checks for the pile to check pile is not is_visble.
        """
        super(InvisibleStack, self).assertStack()
        assert not self.is_visible

    def prepareStack(self):
        """
        prepareStack() -> None

        Overide pile preparation for the canvas to omit initbindings()
        """
        self.prepareView()


class ArbitraryStack(OpenStack):
    """
    Pile support arbitrary access to cards within.

    note::
      Doesn't support hint and demo for non-top cards.

      Can only be used with a YOFFSET.
    """

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, YOFFSET),
          *capability* {}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        cap.setdefault('offset', (0, game.images.offset.yoffset))
        super(ArbitraryStack, self).__init__(x, y, game, **cap)

    def cardsPlay(self, cards):
        """
        cardsPlay(cards:tup) -> bool

        Allow movement regardless.

        :param tuple cards: cards to move from this pile
        :return: cards moveable?
        :rtype: boolean
        """
        return True

    def cardsDrop(self, stacks, event=None):
        """
        cardsDrop(stacks:seq) -> seq

        Check pile has cards, is not cardsBlock() and cardsPlay() to
        a pile in stacks which cardsBuild(). Default allows a card to drop.

        :param sequence stacks: usually Game.s.foundations
        :return: pile and number of cards.
        :rtype: Drop
        """
        i = self._locate(event)
        if self.cardsBlock() or i < 0:
            return (None, 0)
        cards = [self.cards[i]]
        if self.cardsPlay(cards):
            for s in self.game.s['foundations']:
                if s is not self and s.cardsBuild(self, cards):
                    return (s, i)
        return (None, 0)

    def getDragCards(self, index):
        """
        getDragCards(index:int) -> seq

        Overide, allow moving a card from any postion.

        :param integer index: card to be moved from pile.
        :return: cards being moved.
        :rtype: sequence
        """
        return [self.cards[index]]

    def startDrag(self, event, sound=True):
        """
        startDrag(event:obj[, sound:bool=True]) -> None

        Begin drag Stack operation

        :param object event: Canvas Event.
        :keyword boolean sound: sound fx?
        """
        super(ArbitraryStack, self).startDrag(event, sound=sound)
        if CONF_OPT['mouse_type'] == 'point-n-click':
            self.cards[self.game.drag['index']].raise_item()
            self.game.drag['shadows'][0].raise_item()
        else:
            for c in self.cards[self.game.drag['index'] + 1:]:
                c.move(0, -self.CARD_YOFFSET[0])

    def moveCardsBackHandler(self, event, drag):
        """
        moveCardsBackHandler(event:obj, drag:obj) -> None

        Move drag['stack'] back to original position.

        :param object event: canvas event
        :param object drag: drag pile
        """
        i = self.cards.index(drag['cards'][0])
        for card in self.cards[i:]:
            self._position(card)
            card.raise_item()

    def dragMove(self, drag, to_stack, sound=True):
        """
        dragMove(drag:obj, stack:obj[, sound:bool=True]) -> None

        :param object drag: current drag pile
        :param object stack: recieving pile.
        :param boolean sound: sound fx?
        """
        self.game.move(drag['index'], to_stack, sound=sound, frames=0)

    def move_pile(self, ncards, to_stack, **kw):
        """
        move_pile(ncards, to_stack[, frames=-1, shadow=-1]) -> None

        Move card at ncards deep in pile to_stack then fill_stack().
        """
        self.game._move_do(a_move.ASingleCardMove(self, to_stack, cards, **kw),
                            reset=True)  # low level
        self.fill_stack()

    def _quick_from_pile(self, stacks=None, event=None):
        """
        Quickplay: Override to return empty sequence.

        .. \_quick_from_pile([piles:seq(Pile ...), event:obj=None) -> list

        *Parameters:*
          piles: {seq(Piles ...)}
            piles to play on self.
          event: {Event} None
        """
        return []

    def _quick_to_pile(self, stacks=None, event=None):
        """
        Quickplay: Overide return what any single card build on piles by score.

        .. \_quick_to_pile([piles:seq(Pile ...), event:obj=None) -> list

        *Parameters:*
          piles: {seq(Piles ...)}
            piles to build from self.
          event: {Event} None
        """
        game = self.game
        stacks = stacks or game.s['foundations'] + game.sg['dropstacks']
        Logger.info("stacks (%s)", stacks)
        index = self._locate(event)
        if index and index < 0:
            return moves
        pile = self.cards[index:index + 1]
        for stack in stacks:
            if stack is not self and stack.cardsBuild(self, pile):
                score = game._quickplay_(1, self, stack)
                moves.append((score, -len(moves), index, stack))
        return moves


class StackWrapper(object):
    """
    Functor (function object) that creates a new stack_class when called,
    (i.e. wrapping the constructor). __init__ cap will override any
    __call__ similar cap passed.
    """

    def __init__(self, stack_class, **cap):
        assert isinstance(stack_class, (Stack, object,))
        assert issubclass(stack_class, Stack)
        self._class = stack_class
        self._cap = cap

    def __call__(self, x, y, game, **cap):
        """
        Preserve __cap with shallow copy and set cap defaults not set by
        __init__ cap with __call__ cap.

        returns: new stack object with the results as its keywords.
        """
        c = self._cap.copy()
        misc.kwdefault(c, **cap)
        return self._class(x, y, game, **c)

    def __repr__(self):
        """
        helpful class descriptor for debug statements.

        :return: flip |top|?
        :rtype: boolean
        """
        cap = '('
        for k, v in self._cap.items():
            cap += '{}: {}; '.format(k, v)
        return "{0} {1}".format(self._class.__name__, cap)


class WeakStackWrapper(StackWrapper):
    """
    Functor (function object) that creates a new ``stack_class`` when called,
    (i.e. wrapping the constructor). __call__ ``cap`` will override any
    similar __init__ ``cap``.
    """

    def __call__(self, x, y, game, **cap):
        """
        Set cap defaults not set by __call__ cap with __init__ cap.

        returns: new stack object with the results as its keywords.
        """
        misc.kwdefault(cap, **self._cap)
        return self._class(x, y, game, **cap)


class FullStackWrapper(StackWrapper):
    """
    Functor (function object) that creates a new ``stack_class`` when called,
    (i.e. wrapping the constructor). __call__ ``cap`` will be ignored.
    """

    def __call__(self, x, y, game, **cap):
        """
        Ignore __call__ cap and set default with just __init__ cap.

        returns: new stack object with the results as its keywords.
        """
        return self._class(x, y, game, **self._cap)